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" /> <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="/webui/assets/index-C56SCRGK.js"></script> <script type="module" crossorigin src="/webui/assets/index-BxHTAgSP.js"></script>
<link rel="stylesheet" crossorigin href="/webui/assets/index-BE_O4IWQ.css"> <link rel="stylesheet" crossorigin href="/webui/assets/index-BMVv3U43.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View File

@@ -18,7 +18,7 @@ const GraphLabels = () => {
// Track if a fetch is in progress to prevent multiple simultaneous fetches // Track if a fetch is in progress to prevent multiple simultaneous fetches
const fetchInProgressRef = useRef(false) const fetchInProgressRef = useRef(false)
// Fetch labels once on component mount, using global flag to prevent duplicates // Fetch labels and trigger initial data load
useEffect(() => { useEffect(() => {
// Check if we've already attempted to fetch labels in this session // Check if we've already attempted to fetch labels in this session
const labelsFetchAttempted = useGraphStore.getState().labelsFetchAttempted const labelsFetchAttempted = useGraphStore.getState().labelsFetchAttempted
@@ -43,6 +43,14 @@ const GraphLabels = () => {
} }
}, []) // Empty dependency array ensures this only runs once on mount }, []) // 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(() => { const getSearchEngine = useCallback(() => {
// Create search engine // Create search engine
const searchEngine = new MiniSearch({ const searchEngine = new MiniSearch({
@@ -85,13 +93,25 @@ const GraphLabels = () => {
) )
const handleRefresh = useCallback(() => { 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().setGraphDataFetchAttempted(false)
useGraphStore.getState().reset() // 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) useSettingsStore.getState().setQueryLabel(currentLabel)
}, 0)
})
.catch((error) => {
console.error('Failed to refresh labels:', error)
})
}, []) }, [])
return ( return (
@@ -128,22 +148,13 @@ const GraphLabels = () => {
newLabel = '*' newLabel = '*'
} }
// Reset the fetch attempted flag to force a new data fetch // Handle reselecting the same label
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();
}
if (newLabel === currentLabel && newLabel !== '*') { if (newLabel === currentLabel && newLabel !== '*') {
// reselect the same itme means qery all newLabel = '*'
useSettingsStore.getState().setQueryLabel('*')
} else {
useSettingsStore.getState().setQueryLabel(newLabel)
} }
// Update the label, which will trigger the useEffect to handle data loading
useSettingsStore.getState().setQueryLabel(newLabel)
}} }}
clearable={false} // Prevent clearing value on reselect 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 { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
import Checkbox from '@/components/ui/Checkbox' import Checkbox from '@/components/ui/Checkbox'
import Button from '@/components/ui/Button' import Button from '@/components/ui/Button'
@@ -7,7 +7,6 @@ import Input from '@/components/ui/Input'
import { controlButtonVariant } from '@/lib/constants' import { controlButtonVariant } from '@/lib/constants'
import { useSettingsStore } from '@/stores/settings' import { useSettingsStore } from '@/stores/settings'
import { useBackendState } from '@/stores/state'
import { SettingsIcon } from 'lucide-react' import { SettingsIcon } from 'lucide-react'
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -113,7 +112,6 @@ const LabeledNumberInput = ({
*/ */
export default function Settings() { export default function Settings() {
const [opened, setOpened] = useState<boolean>(false) const [opened, setOpened] = useState<boolean>(false)
const [tempApiKey, setTempApiKey] = useState<string>('')
const showPropertyPanel = useSettingsStore.use.showPropertyPanel() const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar() const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
@@ -127,11 +125,6 @@ export default function Settings() {
const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations() const graphLayoutMaxIterations = useSettingsStore.use.graphLayoutMaxIterations()
const enableHealthCheck = useSettingsStore.use.enableHealthCheck() const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
const apiKey = useSettingsStore.use.apiKey()
useEffect(() => {
setTempApiKey(apiKey || '')
}, [apiKey, opened])
const setEnableNodeDrag = useCallback( const setEnableNodeDrag = useCallback(
() => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })), () => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })),
@@ -180,11 +173,22 @@ export default function Settings() {
const setGraphQueryMaxDepth = useCallback((depth: number) => { const setGraphQueryMaxDepth = useCallback((depth: number) => {
if (depth < 1) return if (depth < 1) return
useSettingsStore.setState({ graphQueryMaxDepth: depth }) useSettingsStore.setState({ graphQueryMaxDepth: depth })
const currentLabel = useSettingsStore.getState().queryLabel
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 300)
}, []) }, [])
const setGraphMinDegree = useCallback((degree: number) => { const setGraphMinDegree = useCallback((degree: number) => {
if (degree < 0) return if (degree < 0) return
useSettingsStore.setState({ graphMinDegree: degree }) useSettingsStore.setState({ graphMinDegree: degree })
const currentLabel = useSettingsStore.getState().queryLabel
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 300)
}, []) }, [])
const setGraphLayoutMaxIterations = useCallback((iterations: number) => { const setGraphLayoutMaxIterations = useCallback((iterations: number) => {
@@ -192,26 +196,21 @@ export default function Settings() {
useSettingsStore.setState({ graphLayoutMaxIterations: iterations }) 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 { t } = useTranslation();
const saveSettings = () => setOpened(false);
const toggleSettings = () => setOpened(!opened);
return ( return (
<> <>
<Popover open={opened} onOpenChange={setOpened}> <Popover open={opened}>
<PopoverTrigger asChild> <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 /> <SettingsIcon />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
@@ -293,30 +292,15 @@ export default function Settings() {
onEditFinished={setGraphLayoutMaxIterations} onEditFinished={setGraphLayoutMaxIterations}
/> />
<Separator /> <Separator />
<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 <Button
onClick={setApiKey} onClick={saveSettings}
variant="outline" variant="outline"
size="sm" size="sm"
className="max-h-full shrink-0" className="ml-auto px-4"
> >
{t('graphPanel.sideBar.settings.save')} {t('graphPanel.sideBar.settings.save')}
</Button> </Button>
</form>
</div>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@@ -11,7 +11,7 @@ const SettingsDisplay = () => {
const graphMinDegree = useSettingsStore.use.graphMinDegree() const graphMinDegree = useSettingsStore.use.graphMinDegree()
return ( 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.depth')}: {graphQueryMaxDepth}</div>
<div>{t('graphPanel.sideBar.settings.degree')}: {graphMinDegree}</div> <div>{t('graphPanel.sideBar.settings.degree')}: {graphMinDegree}</div>
</div> </div>

View File

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

View File

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