Merge branch 'feat-node-expand' into webui-node-expansion
This commit is contained in:
1
lightrag/api/webui/assets/index-BEGlBF11.css
Normal file
1
lightrag/api/webui/assets/index-BEGlBF11.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
@@ -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="./assets/index-nzv8EoUv.js"></script>
|
<script type="module" crossorigin src="./assets/index-BpDMZCmZ.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="./assets/index-TPDyec81.css">
|
<link rel="stylesheet" crossorigin href="./assets/index-BEGlBF11.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@@ -123,21 +123,18 @@ async def openai_complete_if_cache(
|
|||||||
|
|
||||||
async def inner():
|
async def inner():
|
||||||
try:
|
try:
|
||||||
_content = ""
|
|
||||||
async for chunk in response:
|
async for chunk in response:
|
||||||
content = chunk.choices[0].delta.content
|
content = chunk.choices[0].delta.content
|
||||||
if content is None:
|
if content is None:
|
||||||
continue
|
continue
|
||||||
if r"\u" in content:
|
if r"\u" in content:
|
||||||
content = safe_unicode_decode(content.encode("utf-8"))
|
content = safe_unicode_decode(content.encode("utf-8"))
|
||||||
_content += content
|
yield content
|
||||||
return _content
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in stream response: {str(e)}")
|
logger.error(f"Error in stream response: {str(e)}")
|
||||||
raise
|
raise
|
||||||
|
|
||||||
response_content = await inner()
|
return inner()
|
||||||
return response_content
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if (
|
if (
|
||||||
|
@@ -7,7 +7,7 @@ import { useLayoutForce, useWorkerLayoutForce } from '@react-sigma/layout-force'
|
|||||||
import { useLayoutForceAtlas2, useWorkerLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
|
import { useLayoutForceAtlas2, useWorkerLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
|
||||||
import { useLayoutNoverlap, useWorkerLayoutNoverlap } from '@react-sigma/layout-noverlap'
|
import { useLayoutNoverlap, useWorkerLayoutNoverlap } from '@react-sigma/layout-noverlap'
|
||||||
import { useLayoutRandom } from '@react-sigma/layout-random'
|
import { useLayoutRandom } from '@react-sigma/layout-random'
|
||||||
import { useCallback, useMemo, useState, useEffect } from 'react'
|
import { useCallback, useMemo, useState, useEffect, useRef } from 'react'
|
||||||
|
|
||||||
import Button from '@/components/ui/Button'
|
import Button from '@/components/ui/Button'
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
|
||||||
@@ -15,7 +15,7 @@ import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui
|
|||||||
import { controlButtonVariant } from '@/lib/constants'
|
import { controlButtonVariant } from '@/lib/constants'
|
||||||
import { useSettingsStore } from '@/stores/settings'
|
import { useSettingsStore } from '@/stores/settings'
|
||||||
|
|
||||||
import { GripIcon, PlayIcon, PauseIcon, RefreshCwIcon } from 'lucide-react'
|
import { GripIcon, PlayIcon, PauseIcon } from 'lucide-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
type LayoutName =
|
type LayoutName =
|
||||||
@@ -26,43 +26,161 @@ type LayoutName =
|
|||||||
| 'Force Directed'
|
| 'Force Directed'
|
||||||
| 'Force Atlas'
|
| 'Force Atlas'
|
||||||
|
|
||||||
const WorkerLayoutControl = ({ layout, autoRunFor }: WorkerLayoutControlProps) => {
|
// Extend WorkerLayoutControlProps to include mainLayout
|
||||||
|
interface ExtendedWorkerLayoutControlProps extends WorkerLayoutControlProps {
|
||||||
|
mainLayout: LayoutHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkerLayoutControl = ({ layout, autoRunFor, mainLayout }: ExtendedWorkerLayoutControlProps) => {
|
||||||
const sigma = useSigma()
|
const sigma = useSigma()
|
||||||
const { stop, start, isRunning } = layout
|
// Use local state to track animation running status
|
||||||
|
const [isRunning, setIsRunning] = useState(false)
|
||||||
|
// Timer reference for animation
|
||||||
|
const animationTimerRef = useRef<number | null>(null)
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
// Function to update node positions using the layout algorithm
|
||||||
|
const updatePositions = useCallback(() => {
|
||||||
|
if (!sigma) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const graph = sigma.getGraph()
|
||||||
|
if (!graph || graph.order === 0) return
|
||||||
|
|
||||||
|
// Use mainLayout to get positions, similar to refreshLayout function
|
||||||
|
// console.log('Getting positions from mainLayout')
|
||||||
|
const positions = mainLayout.positions()
|
||||||
|
|
||||||
|
// Animate nodes to new positions
|
||||||
|
// console.log('Updating node positions with layout algorithm')
|
||||||
|
animateNodes(graph, positions, { duration: 300 }) // Reduced duration for more frequent updates
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating positions:', error)
|
||||||
|
// Stop animation if there's an error
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
window.clearInterval(animationTimerRef.current)
|
||||||
|
animationTimerRef.current = null
|
||||||
|
setIsRunning(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [sigma, mainLayout])
|
||||||
|
|
||||||
|
// Improved click handler that uses our own animation timer
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
if (isRunning) {
|
||||||
|
// Stop the animation
|
||||||
|
console.log('Stopping layout animation')
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
window.clearInterval(animationTimerRef.current)
|
||||||
|
animationTimerRef.current = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to kill the layout algorithm if it's running
|
||||||
|
try {
|
||||||
|
if (typeof layout.kill === 'function') {
|
||||||
|
layout.kill()
|
||||||
|
console.log('Layout algorithm killed')
|
||||||
|
} else if (typeof layout.stop === 'function') {
|
||||||
|
layout.stop()
|
||||||
|
console.log('Layout algorithm stopped')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping layout algorithm:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsRunning(false)
|
||||||
|
} else {
|
||||||
|
// Start the animation
|
||||||
|
console.log('Starting layout animation')
|
||||||
|
|
||||||
|
// Initial position update
|
||||||
|
updatePositions()
|
||||||
|
|
||||||
|
// Set up interval for continuous updates
|
||||||
|
animationTimerRef.current = window.setInterval(() => {
|
||||||
|
updatePositions()
|
||||||
|
}, 200) // Reduced interval to create overlapping animations for smoother transitions
|
||||||
|
|
||||||
|
setIsRunning(true)
|
||||||
|
|
||||||
|
// Set a timeout to automatically stop the animation after 3 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
console.log('Auto-stopping layout animation after 3 seconds')
|
||||||
|
window.clearInterval(animationTimerRef.current)
|
||||||
|
animationTimerRef.current = null
|
||||||
|
setIsRunning(false)
|
||||||
|
|
||||||
|
// Try to stop the layout algorithm
|
||||||
|
try {
|
||||||
|
if (typeof layout.kill === 'function') {
|
||||||
|
layout.kill()
|
||||||
|
} else if (typeof layout.stop === 'function') {
|
||||||
|
layout.stop()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error stopping layout algorithm:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
}, [isRunning, layout, updatePositions])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init component when Sigma or component settings change.
|
* Init component when Sigma or component settings change.
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sigma) {
|
if (!sigma) {
|
||||||
|
console.log('No sigma instance available')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// we run the algo
|
// Auto-run if specified
|
||||||
let timeout: number | null = null
|
let timeout: number | null = null
|
||||||
if (autoRunFor !== undefined && autoRunFor > -1 && sigma.getGraph().order > 0) {
|
if (autoRunFor !== undefined && autoRunFor > -1 && sigma.getGraph().order > 0) {
|
||||||
start()
|
console.log('Auto-starting layout animation')
|
||||||
// set a timeout to stop it
|
|
||||||
timeout =
|
|
||||||
autoRunFor > 0
|
|
||||||
? window.setTimeout(() => { stop() }, autoRunFor) // prettier-ignore
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
//cleaning
|
// Initial position update
|
||||||
return () => {
|
updatePositions()
|
||||||
stop()
|
|
||||||
if (timeout) {
|
// Set up interval for continuous updates
|
||||||
clearTimeout(timeout)
|
animationTimerRef.current = window.setInterval(() => {
|
||||||
|
updatePositions()
|
||||||
|
}, 200) // Reduced interval to create overlapping animations for smoother transitions
|
||||||
|
|
||||||
|
setIsRunning(true)
|
||||||
|
|
||||||
|
// Set a timeout to stop it if autoRunFor > 0
|
||||||
|
if (autoRunFor > 0) {
|
||||||
|
timeout = window.setTimeout(() => {
|
||||||
|
console.log('Auto-stopping layout animation after timeout')
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
window.clearInterval(animationTimerRef.current)
|
||||||
|
animationTimerRef.current = null
|
||||||
|
}
|
||||||
|
setIsRunning(false)
|
||||||
|
}, autoRunFor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [autoRunFor, start, stop, sigma])
|
|
||||||
|
// Cleanup function
|
||||||
|
return () => {
|
||||||
|
// console.log('Cleaning up WorkerLayoutControl')
|
||||||
|
if (animationTimerRef.current) {
|
||||||
|
window.clearInterval(animationTimerRef.current)
|
||||||
|
animationTimerRef.current = null
|
||||||
|
}
|
||||||
|
if (timeout) {
|
||||||
|
window.clearTimeout(timeout)
|
||||||
|
}
|
||||||
|
setIsRunning(false)
|
||||||
|
}
|
||||||
|
}, [autoRunFor, sigma, updatePositions])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => (isRunning ? stop() : start())}
|
onClick={handleClick}
|
||||||
tooltip={isRunning ? t('graphPanel.sideBar.layoutsControl.stopAnimation') : t('graphPanel.sideBar.layoutsControl.startAnimation')}
|
tooltip={isRunning ? t('graphPanel.sideBar.layoutsControl.stopAnimation') : t('graphPanel.sideBar.layoutsControl.startAnimation')}
|
||||||
variant={controlButtonVariant}
|
variant={controlButtonVariant}
|
||||||
>
|
>
|
||||||
@@ -86,7 +204,17 @@ const LayoutsControl = () => {
|
|||||||
const layoutCirclepack = useLayoutCirclepack()
|
const layoutCirclepack = useLayoutCirclepack()
|
||||||
const layoutRandom = useLayoutRandom()
|
const layoutRandom = useLayoutRandom()
|
||||||
const layoutNoverlap = useLayoutNoverlap({ settings: { margin: 1 } })
|
const layoutNoverlap = useLayoutNoverlap({ settings: { margin: 1 } })
|
||||||
const layoutForce = useLayoutForce({ maxIterations: maxIterations })
|
// Add parameters for Force Directed layout to improve convergence
|
||||||
|
const layoutForce = useLayoutForce({
|
||||||
|
maxIterations: maxIterations * 3, // Triple the iterations for better convergence
|
||||||
|
settings: {
|
||||||
|
attraction: 0.0003, // Lower attraction force to reduce oscillation
|
||||||
|
repulsion: 0.05, // Lower repulsion force to reduce oscillation
|
||||||
|
gravity: 0.01, // Increase gravity to make nodes converge to center faster
|
||||||
|
inertia: 0.4, // Lower inertia to add damping effect
|
||||||
|
maxMove: 100 // Limit maximum movement per step to prevent large jumps
|
||||||
|
}
|
||||||
|
})
|
||||||
const layoutForceAtlas2 = useLayoutForceAtlas2({ iterations: maxIterations })
|
const layoutForceAtlas2 = useLayoutForceAtlas2({ iterations: maxIterations })
|
||||||
const workerNoverlap = useWorkerLayoutNoverlap()
|
const workerNoverlap = useWorkerLayoutNoverlap()
|
||||||
const workerForce = useWorkerLayoutForce()
|
const workerForce = useWorkerLayoutForce()
|
||||||
@@ -130,34 +258,35 @@ const LayoutsControl = () => {
|
|||||||
|
|
||||||
const runLayout = useCallback(
|
const runLayout = useCallback(
|
||||||
(newLayout: LayoutName) => {
|
(newLayout: LayoutName) => {
|
||||||
console.debug(newLayout)
|
console.debug('Running layout:', newLayout)
|
||||||
const { positions } = layouts[newLayout].layout
|
const { positions } = layouts[newLayout].layout
|
||||||
animateNodes(sigma.getGraph(), positions(), { duration: 500 })
|
|
||||||
setLayout(newLayout)
|
try {
|
||||||
|
const graph = sigma.getGraph()
|
||||||
|
if (!graph) {
|
||||||
|
console.error('No graph available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = positions()
|
||||||
|
console.log('Positions calculated, animating nodes')
|
||||||
|
animateNodes(graph, pos, { duration: 400 })
|
||||||
|
setLayout(newLayout)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error running layout:', error)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[layouts, sigma]
|
[layouts, sigma]
|
||||||
)
|
)
|
||||||
|
|
||||||
const refreshLayout = useCallback(() => {
|
|
||||||
if (!sigma) return
|
|
||||||
const graph = sigma.getGraph()
|
|
||||||
const positions = layoutForceAtlas2.positions()
|
|
||||||
animateNodes(graph, positions, { duration: 500 })
|
|
||||||
}, [sigma, layoutForceAtlas2])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
|
||||||
variant={controlButtonVariant}
|
|
||||||
tooltip={t('graphPanel.sideBar.settings.refreshLayout')}
|
|
||||||
size="icon"
|
|
||||||
onClick={refreshLayout}
|
|
||||||
>
|
|
||||||
<RefreshCwIcon />
|
|
||||||
</Button>
|
|
||||||
<div>
|
<div>
|
||||||
{layouts[layout] && 'worker' in layouts[layout] && (
|
{layouts[layout] && 'worker' in layouts[layout] && (
|
||||||
<WorkerLayoutControl layout={layouts[layout].worker!} />
|
<WorkerLayoutControl
|
||||||
|
layout={layouts[layout].worker!}
|
||||||
|
mainLayout={layouts[layout].layout}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@@ -11,18 +11,16 @@ const PopoverContent = React.forwardRef<
|
|||||||
React.ComponentRef<typeof PopoverPrimitive.Content>,
|
React.ComponentRef<typeof PopoverPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
|
||||||
<PopoverPrimitive.Portal>
|
<PopoverPrimitive.Content
|
||||||
<PopoverPrimitive.Content
|
ref={ref}
|
||||||
ref={ref}
|
align={align}
|
||||||
align={align}
|
sideOffset={sideOffset}
|
||||||
sideOffset={sideOffset}
|
className={cn(
|
||||||
className={cn(
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 rounded-md border p-4 shadow-md outline-none',
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 rounded-md border p-4 shadow-md outline-none',
|
className
|
||||||
className
|
)}
|
||||||
)}
|
{...props}
|
||||||
{...props}
|
/>
|
||||||
/>
|
|
||||||
</PopoverPrimitive.Portal>
|
|
||||||
))
|
))
|
||||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ const TooltipContent = React.forwardRef<
|
|||||||
side={side}
|
side={side}
|
||||||
align={align}
|
align={align}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-[60vh] overflow-y-auto whitespace-pre-wrap break-words rounded-md border px-3 py-2 text-sm shadow-md',
|
'bg-popover text-popover-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-[60vh] overflow-y-auto whitespace-pre-wrap break-words rounded-md border px-3 py-2 text-sm shadow-md z-60',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
@@ -101,8 +101,8 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"layoutsControl": {
|
"layoutsControl": {
|
||||||
"startAnimation": "Start the layout animation",
|
"startAnimation": "Continue layout animation",
|
||||||
"stopAnimation": "Stop the layout animation",
|
"stopAnimation": "Stop layout animation",
|
||||||
"layoutGraph": "Layout Graph",
|
"layoutGraph": "Layout Graph",
|
||||||
"layouts": {
|
"layouts": {
|
||||||
"Circular": "Circular",
|
"Circular": "Circular",
|
||||||
|
@@ -99,7 +99,7 @@
|
|||||||
"resetZoom": "重置缩放"
|
"resetZoom": "重置缩放"
|
||||||
},
|
},
|
||||||
"layoutsControl": {
|
"layoutsControl": {
|
||||||
"startAnimation": "开始布局动画",
|
"startAnimation": "继续布局动画",
|
||||||
"stopAnimation": "停止布局动画",
|
"stopAnimation": "停止布局动画",
|
||||||
"layoutGraph": "图布局",
|
"layoutGraph": "图布局",
|
||||||
"layouts": {
|
"layouts": {
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"Random": "随机",
|
"Random": "随机",
|
||||||
"Noverlaps": "无重叠",
|
"Noverlaps": "无重叠",
|
||||||
"Force Directed": "力导向",
|
"Force Directed": "力导向",
|
||||||
"Force Atlas": "力图"
|
"Force Atlas": "力地图"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fullScreenControl": {
|
"fullScreenControl": {
|
||||||
|
Reference in New Issue
Block a user