Merge pull request #1126 from danielaskdd/improve-graph-reload

Improve graph reload
This commit is contained in:
Daniel.y
2025-03-20 04:33:04 +08:00
committed by GitHub
9 changed files with 141 additions and 163 deletions

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

View File

@@ -8,8 +8,8 @@
<link rel="icon" type="image/svg+xml" href="logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lightrag</title>
<script type="module" crossorigin src="/webui/assets/index-C56SCRGK.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-BE_O4IWQ.css">
<script type="module" crossorigin src="/webui/assets/index-BxHTAgSP.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-BMVv3U43.css">
</head>
<body>
<div id="root"></div>

View File

@@ -18,7 +18,7 @@ const GraphLabels = () => {
// Track if a fetch is in progress to prevent multiple simultaneous fetches
const fetchInProgressRef = useRef(false)
// Fetch labels once on component mount, using global flag to prevent duplicates
// Fetch labels and trigger initial data load
useEffect(() => {
// Check if we've already attempted to fetch labels in this session
const labelsFetchAttempted = useGraphStore.getState().labelsFetchAttempted
@@ -43,6 +43,14 @@ const GraphLabels = () => {
}
}, []) // Empty dependency array ensures this only runs once on mount
// Trigger data load when labels are loaded
useEffect(() => {
if (labelsLoadedRef.current) {
// Reset the fetch attempted flag to force a new data fetch
useGraphStore.getState().setGraphDataFetchAttempted(false)
}
}, [label])
const getSearchEngine = useCallback(() => {
// Create search engine
const searchEngine = new MiniSearch({
@@ -85,13 +93,25 @@ const GraphLabels = () => {
)
const handleRefresh = useCallback(() => {
const currentLabel = useSettingsStore.getState().queryLabel
// Reset labels fetch status to allow fetching labels again
useGraphStore.getState().setLabelsFetchAttempted(false)
// Reset graph data fetch status directly, not depending on allDatabaseLabels changes
useGraphStore.getState().setGraphDataFetchAttempted(false)
useGraphStore.getState().reset()
useSettingsStore.getState().setQueryLabel(currentLabel)
// Fetch all labels again
useGraphStore.getState().fetchAllDatabaseLabels()
.then(() => {
// Trigger a graph data reload by changing the query label back and forth
const currentLabel = useSettingsStore.getState().queryLabel
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 0)
})
.catch((error) => {
console.error('Failed to refresh labels:', error)
})
}, [])
return (
@@ -128,22 +148,13 @@ const GraphLabels = () => {
newLabel = '*'
}
// Reset the fetch attempted flag to force a new data fetch
useGraphStore.getState().setGraphDataFetchAttempted(false)
// Clear current graph data to ensure complete reload when label changes
if (newLabel !== currentLabel) {
const graphStore = useGraphStore.getState();
// Reset the all graph objects and status
graphStore.reset();
}
// Handle reselecting the same label
if (newLabel === currentLabel && newLabel !== '*') {
// reselect the same itme means qery all
useSettingsStore.getState().setQueryLabel('*')
} else {
useSettingsStore.getState().setQueryLabel(newLabel)
newLabel = '*'
}
// Update the label, which will trigger the useEffect to handle data loading
useSettingsStore.getState().setQueryLabel(newLabel)
}}
clearable={false} // Prevent clearing value on reselect
/>

View File

@@ -1,4 +1,4 @@
import { useState, useCallback, useEffect } from 'react'
import { useState, useCallback} from 'react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
import Checkbox from '@/components/ui/Checkbox'
import Button from '@/components/ui/Button'
@@ -7,7 +7,6 @@ import Input from '@/components/ui/Input'
import { controlButtonVariant } from '@/lib/constants'
import { useSettingsStore } from '@/stores/settings'
import { useBackendState } from '@/stores/state'
import { SettingsIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next';
@@ -113,7 +112,6 @@ const LabeledNumberInput = ({
*/
export default function Settings() {
const [opened, setOpened] = useState<boolean>(false)
const [tempApiKey, setTempApiKey] = useState<string>('')
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
@@ -127,11 +125,6 @@ export default function Settings() {
const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
const apiKey = useSettingsStore.use.apiKey()
useEffect(() => {
setTempApiKey(apiKey || '')
}, [apiKey, opened])
const setEnableNodeDrag = useCallback(
() => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })),
@@ -180,11 +173,22 @@ export default function Settings() {
const setGraphQueryMaxDepth = useCallback((depth: number) => {
if (depth < 1) return
useSettingsStore.setState({ graphQueryMaxDepth: depth })
const currentLabel = useSettingsStore.getState().queryLabel
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 300)
}, [])
const setGraphMinDegree = useCallback((degree: number) => {
if (degree < 0) return
useSettingsStore.setState({ graphMinDegree: degree })
const currentLabel = useSettingsStore.getState().queryLabel
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 300)
}, [])
const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
@@ -192,26 +196,21 @@ export default function Settings() {
useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
}, [])
const setApiKey = useCallback(async () => {
useSettingsStore.setState({ apiKey: tempApiKey || null })
await useBackendState.getState().check()
setOpened(false)
}, [tempApiKey])
const handleTempApiKeyChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setTempApiKey(e.target.value)
},
[setTempApiKey]
)
const { t } = useTranslation();
const saveSettings = () => setOpened(false);
const toggleSettings = () => setOpened(!opened);
return (
<>
<Popover open={opened} onOpenChange={setOpened}>
<Popover open={opened}>
<PopoverTrigger asChild>
<Button variant={controlButtonVariant} tooltip={t('graphPanel.sideBar.settings.settings')} size="icon">
<Button
variant={controlButtonVariant}
tooltip={t('graphPanel.sideBar.settings.settings')}
size="icon"
onClick={toggleSettings}
>
<SettingsIcon />
</Button>
</PopoverTrigger>
@@ -293,30 +292,15 @@ export default function Settings() {
onEditFinished={setGraphLayoutMaxIterations}
/>
<Separator />
<Button
onClick={saveSettings}
variant="outline"
size="sm"
className="ml-auto px-4"
>
{t('graphPanel.sideBar.settings.save')}
</Button>
<div className="flex flex-col gap-2">
<label className="text-sm font-medium">{t('graphPanel.sideBar.settings.apiKey')}</label>
<form className="flex h-6 gap-2" onSubmit={(e) => e.preventDefault()}>
<div className="w-0 flex-1">
<Input
type="password"
value={tempApiKey}
onChange={handleTempApiKeyChange}
placeholder={t('graphPanel.sideBar.settings.enterYourAPIkey')}
className="max-h-full w-full min-w-0"
autoComplete="off"
/>
</div>
<Button
onClick={setApiKey}
variant="outline"
size="sm"
className="max-h-full shrink-0"
>
{t('graphPanel.sideBar.settings.save')}
</Button>
</form>
</div>
</div>
</PopoverContent>
</Popover>

View File

@@ -11,7 +11,7 @@ const SettingsDisplay = () => {
const graphMinDegree = useSettingsStore.use.graphMinDegree()
return (
<div className="absolute bottom-2 left-[calc(2rem+2.5rem)] flex items-center gap-2 text-xs text-gray-400">
<div className="absolute bottom-4 left-[calc(1rem+2.5rem)] flex items-center gap-2 text-xs text-gray-400">
<div>{t('graphPanel.sideBar.settings.depth')}: {graphQueryMaxDepth}</div>
<div>{t('graphPanel.sideBar.settings.degree')}: {graphMinDegree}</div>
</div>

View File

@@ -189,19 +189,10 @@ const useLightrangeGraph = () => {
const nodeToExpand = useGraphStore.use.nodeToExpand()
const nodeToPrune = useGraphStore.use.nodeToPrune()
// Track previous parameters to detect actual changes
const prevParamsRef = useRef({ queryLabel, maxQueryDepth, minDegree })
// Use ref to track if data has been loaded and initial load
const dataLoadedRef = useRef(false)
const initialLoadRef = useRef(false)
// Check if parameters have changed
const paramsChanged =
prevParamsRef.current.queryLabel !== queryLabel ||
prevParamsRef.current.maxQueryDepth !== maxQueryDepth ||
prevParamsRef.current.minDegree !== minDegree
const getNode = useCallback(
(nodeId: string) => {
return rawGraph?.getNode(nodeId) || null
@@ -219,30 +210,27 @@ const useLightrangeGraph = () => {
// Track if a fetch is in progress to prevent multiple simultaneous fetches
const fetchInProgressRef = useRef(false)
// Data fetching logic - simplified but preserving TAB visibility check
// Reset graph when query label is cleared
useEffect(() => {
// Skip if fetch is already in progress
if (fetchInProgressRef.current) {
return
}
// If there's no query label, reset the graph
if (!queryLabel) {
if (rawGraph !== null || sigmaGraph !== null) {
const state = useGraphStore.getState()
state.reset()
state.setGraphDataFetchAttempted(false)
state.setLabelsFetchAttempted(false)
}
if (!queryLabel && (rawGraph !== null || sigmaGraph !== null)) {
const state = useGraphStore.getState()
state.reset()
state.setGraphDataFetchAttempted(false)
state.setLabelsFetchAttempted(false)
dataLoadedRef.current = false
initialLoadRef.current = false
}
}, [queryLabel, rawGraph, sigmaGraph])
// Data fetching logic
useEffect(() => {
// Skip if fetch is already in progress or no query label
if (fetchInProgressRef.current || !queryLabel) {
return
}
// Check if parameters have changed
if (!isFetching && !fetchInProgressRef.current &&
(paramsChanged || !useGraphStore.getState().graphDataFetchAttempted)) {
// Only fetch data when graphDataFetchAttempted is false (avoids re-fetching on vite dev mode)
if (!isFetching && !useGraphStore.getState().graphDataFetchAttempted) {
// Set flags
fetchInProgressRef.current = true
useGraphStore.getState().setGraphDataFetchAttempted(true)
@@ -258,9 +246,6 @@ const useLightrangeGraph = () => {
})
}
// Update parameter reference
prevParamsRef.current = { queryLabel, maxQueryDepth, minDegree }
console.log('Fetching graph data...')
// Use a local copy of the parameters
@@ -283,8 +268,6 @@ const useLightrangeGraph = () => {
state.setSigmaGraph(newSigmaGraph)
state.setRawGraph(data)
// No longer need to extract labels from graph data
// Update flags
dataLoadedRef.current = true
initialLoadRef.current = true
@@ -305,7 +288,7 @@ const useLightrangeGraph = () => {
state.setGraphDataFetchAttempted(false)
})
}
}, [queryLabel, maxQueryDepth, minDegree, isFetching, paramsChanged, rawGraph, sigmaGraph])
}, [queryLabel, maxQueryDepth, minDegree, isFetching])
// Handle node expansion
useEffect(() => {

View File

@@ -99,7 +99,7 @@
"hideUnselectedEdges": "隐藏未选中的边",
"edgeEvents": "边事件",
"maxQueryDepth": "最大查询深度",
"minDegree": "最小数",
"minDegree": "最小邻边数",
"maxLayoutIterations": "最大布局迭代次数",
"depth": "深度",
"degree": "邻边",