diff --git a/lightrag_webui/package.json b/lightrag_webui/package.json
index 6016174c..7ed9ffcb 100644
--- a/lightrag_webui/package.json
+++ b/lightrag_webui/package.json
@@ -8,105 +8,98 @@
"build": "bunx --bun vite build",
"lint": "eslint .",
"preview": "bunx --bun vite preview",
- "test": "bun test",
- "test:watch": "bun test --watch",
- "test:coverage": "bun test --coverage",
"dev-no-bun": "vite",
"build-no-bun": "vite build --emptyOutDir",
"preview-no-bun": "vite preview"
},
"dependencies": {
- "@faker-js/faker": "^9.9.0",
- "@radix-ui/react-alert-dialog": "^1.1.15",
- "@radix-ui/react-checkbox": "^1.3.3",
- "@radix-ui/react-dialog": "^1.1.15",
- "@radix-ui/react-popover": "^1.1.15",
- "@radix-ui/react-progress": "^1.1.8",
- "@radix-ui/react-scroll-area": "^1.2.10",
- "@radix-ui/react-select": "^2.2.6",
- "@radix-ui/react-separator": "^1.1.8",
- "@radix-ui/react-slot": "^1.2.4",
- "@radix-ui/react-tabs": "^1.1.13",
- "@radix-ui/react-tooltip": "^1.2.8",
- "@radix-ui/react-use-controllable-state": "^1.2.2",
- "@react-sigma/core": "^5.0.4",
- "@react-sigma/graph-search": "^5.0.4",
- "@react-sigma/layout-circlepack": "^5.0.4",
- "@react-sigma/layout-circular": "^5.0.4",
- "@react-sigma/layout-force": "^5.0.4",
- "@react-sigma/layout-forceatlas2": "^5.0.4",
- "@react-sigma/layout-noverlap": "^5.0.4",
- "@react-sigma/layout-random": "^5.0.4",
- "@react-sigma/minimap": "^5.0.5",
+ "@faker-js/faker": "^9.5.0",
+ "@radix-ui/react-alert-dialog": "^1.1.6",
+ "@radix-ui/react-checkbox": "^1.1.4",
+ "@radix-ui/react-dialog": "^1.1.6",
+ "@radix-ui/react-popover": "^1.1.6",
+ "@radix-ui/react-progress": "^1.1.2",
+ "@radix-ui/react-scroll-area": "^1.2.3",
+ "@radix-ui/react-select": "^2.1.6",
+ "@radix-ui/react-separator": "^1.1.2",
+ "@radix-ui/react-slot": "^1.1.2",
+ "@radix-ui/react-tabs": "^1.1.3",
+ "@radix-ui/react-tooltip": "^1.1.8",
+ "@radix-ui/react-use-controllable-state": "^1.1.0",
+ "@react-sigma/core": "^5.0.2",
+ "@react-sigma/graph-search": "^5.0.3",
+ "@react-sigma/layout-circlepack": "^5.0.2",
+ "@react-sigma/layout-circular": "^5.0.2",
+ "@react-sigma/layout-force": "^5.0.2",
+ "@react-sigma/layout-forceatlas2": "^5.0.2",
+ "@react-sigma/layout-noverlap": "^5.0.2",
+ "@react-sigma/layout-random": "^5.0.2",
+ "@react-sigma/minimap": "^5.0.2",
"@sigma/edge-curve": "^3.1.0",
"@sigma/node-border": "^3.0.0",
- "@tanstack/react-table": "^8.21.3",
- "axios": "^1.13.2",
+ "axios": "^1.7.9",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
- "cmdk": "^1.1.1",
+ "cmdk": "^1.0.4",
"graphology": "^0.26.0",
"graphology-generators": "^0.11.2",
"graphology-layout": "^0.6.1",
"graphology-layout-force": "^0.2.4",
"graphology-layout-forceatlas2": "^0.10.1",
"graphology-layout-noverlap": "^0.4.2",
- "i18next": "^25.6.3",
- "katex": "^0.16.25",
- "mermaid": "^11.12.1",
- "lucide-react": "^0.554.0",
- "minisearch": "^7.2.0",
- "react": "^19.2.0",
- "react-dom": "^19.2.0",
- "react-dropzone": "^14.3.8",
- "react-error-boundary": "^6.0.0",
- "react-i18next": "^16.2.3",
+ "i18next": "^24.2.2",
+ "katex": "^0.16.22",
+ "lucide-react": "^0.475.0",
+ "mermaid": "^11.9.0",
+ "minisearch": "^7.1.2",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0",
+ "react-dropzone": "^14.3.6",
+ "react-error-boundary": "^5.0.0",
+ "react-i18next": "^15.4.1",
"react-markdown": "^9.1.0",
- "react-number-format": "^5.4.4",
- "react-router-dom": "^7.9.6",
+ "react-number-format": "^5.4.3",
+ "react-router-dom": "^7.3.0",
"react-select": "^5.10.2",
- "react-syntax-highlighter": "^15.6.6",
+ "react-syntax-highlighter": "^15.6.1",
"rehype-katex": "^7.0.1",
- "rehype-raw": "^7.0.0",
"rehype-react": "^8.0.0",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"seedrandom": "^3.0.5",
- "sigma": "^3.0.2",
+ "sigma": "^3.0.1",
"sonner": "^1.7.4",
- "tailwind-merge": "^3.4.0",
- "tailwind-scrollbar": "^4.0.2",
+ "tailwind-merge": "^3.0.2",
+ "tailwind-scrollbar": "^4.0.1",
"typography": "^0.16.24",
- "unist-util-visit": "^5.0.0",
- "zustand": "^5.0.8"
+ "zustand": "^5.0.3"
},
"devDependencies": {
- "@eslint/js": "^9.39.1",
- "@stylistic/eslint-plugin-js": "^4.4.1",
- "@types/bun": "^1.3.3",
- "@tailwindcss/vite": "^4.1.17",
+ "@eslint/js": "^9.21.0",
+ "@stylistic/eslint-plugin-js": "^3.1.0",
+ "@tailwindcss/vite": "^4.0.8",
+ "@types/bun": "^1.2.3",
"@types/katex": "^0.16.7",
- "@types/node": "^22.18.9",
- "@tailwindcss/typography": "^0.5.15",
- "@types/react": "^19.2.7",
- "@types/react-dom": "^19.2.3",
+ "@types/node": "^22.13.5",
+ "@types/react": "^19.0.10",
+ "@types/react-dom": "^19.0.4",
"@types/react-i18next": "^8.1.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/seedrandom": "^3.0.8",
- "@vitejs/plugin-react-swc": "^4.2.2",
- "eslint": "^9.39.1",
- "eslint-config-prettier": "^10.1.8",
- "eslint-plugin-react": "^7.37.5",
- "eslint-plugin-react-hooks": "^7.0.1",
- "eslint-plugin-react-refresh": "^0.4.24",
- "globals": "^16.5.0",
+ "@vitejs/plugin-react-swc": "^3.8.0",
+ "eslint": "^9.21.0",
+ "eslint-config-prettier": "^10.0.1",
+ "eslint-plugin-react": "^7.37.4",
+ "eslint-plugin-react-hooks": "^5.1.0",
+ "eslint-plugin-react-refresh": "^0.4.19",
+ "globals": "^15.15.0",
"graphology-types": "^0.24.8",
- "prettier": "^3.6.2",
- "prettier-plugin-tailwindcss": "^0.7.1",
- "typescript-eslint": "^8.48.0",
- "tailwindcss": "^4.1.17",
+ "prettier": "^3.5.2",
+ "prettier-plugin-tailwindcss": "^0.6.11",
+ "tailwindcss": "^4.0.8",
"tailwindcss-animate": "^1.0.7",
- "typescript": "~5.9.3",
- "vite": "^7.1.12"
+ "typescript": "~5.7.3",
+ "typescript-eslint": "^8.24.1",
+ "vite": "^6.1.1"
}
}
diff --git a/lightrag_webui/src/components/graph/GraphControl.tsx b/lightrag_webui/src/components/graph/GraphControl.tsx
index 20d6d603..6d040c47 100644
--- a/lightrag_webui/src/components/graph/GraphControl.tsx
+++ b/lightrag_webui/src/components/graph/GraphControl.tsx
@@ -2,7 +2,7 @@ import { useRegisterEvents, useSetSettings, useSigma } from '@react-sigma/core'
import { AbstractGraph } from 'graphology-types'
// import { useLayoutCircular } from '@react-sigma/layout-circular'
import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
-import { useEffect } from 'react'
+import { useEffect, useState } from 'react'
// import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph'
import { EdgeType, NodeType } from '@/hooks/useLightragGraph'
@@ -43,6 +43,20 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
const selectedEdge = useGraphStore.use.selectedEdge()
const focusedEdge = useGraphStore.use.focusedEdge()
const sigmaGraph = useGraphStore.use.sigmaGraph()
+
+ // Track system theme changes when theme is set to 'system'
+ const [systemThemeIsDark, setSystemThemeIsDark] = useState(() =>
+ window.matchMedia('(prefers-color-scheme: dark)').matches
+ )
+
+ useEffect(() => {
+ if (theme === 'system') {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
+ const handler = (e: MediaQueryListEvent) => setSystemThemeIsDark(e.matches)
+ mediaQuery.addEventListener('change', handler)
+ return () => mediaQuery.removeEventListener('change', handler)
+ }
+ }, [theme])
/**
* When component mount or maxIterations changes
@@ -204,7 +218,9 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
* => Setting the sigma reducers
*/
useEffect(() => {
- const isDarkTheme = theme === 'dark'
+ // Check if dark mode is actually applied (handles both 'dark' theme and 'system' theme when OS is dark)
+ const isDarkTheme = theme === 'dark' ||
+ (theme === 'system' && window.document.documentElement.classList.contains('dark'))
const labelColor = isDarkTheme ? Constants.labelColorDarkTheme : undefined
const edgeColor = isDarkTheme ? Constants.edgeColorDarkTheme : undefined
@@ -329,6 +345,7 @@ const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean })
sigma,
disableHoverEffect,
theme,
+ systemThemeIsDark,
hideUnselectedEdges,
enableEdgeEvents,
renderEdgeLabels,
diff --git a/lightrag_webui/src/components/graph/Settings.tsx b/lightrag_webui/src/components/graph/Settings.tsx
index 37a36305..41da2f22 100644
--- a/lightrag_webui/src/components/graph/Settings.tsx
+++ b/lightrag_webui/src/components/graph/Settings.tsx
@@ -7,8 +7,10 @@ import Input from '@/components/ui/Input'
import { controlButtonVariant } from '@/lib/constants'
import { useSettingsStore } from '@/stores/settings'
+import { useGraphStore } from '@/stores/graph'
+import useRandomGraph from '@/hooks/useRandomGraph'
-import { SettingsIcon, Undo2 } from 'lucide-react'
+import { SettingsIcon, Undo2, Shuffle } from 'lucide-react'
import { useTranslation } from 'react-i18next';
/**
@@ -163,6 +165,9 @@ export default function Settings() {
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
+ // Random graph functionality for development/testing
+ const { randomGraph } = useRandomGraph()
+
const setEnableNodeDrag = useCallback(
() => useSettingsStore.setState((pre) => ({ enableNodeDrag: !pre.enableNodeDrag })),
[]
@@ -228,6 +233,11 @@ export default function Settings() {
useSettingsStore.setState({ graphLayoutMaxIterations: iterations })
}, [])
+ const handleGenerateRandomGraph = useCallback(() => {
+ const graph = randomGraph()
+ useGraphStore.getState().setSigmaGraph(graph)
+ }, [randomGraph])
+
const { t } = useTranslation();
const saveSettings = () => setOpened(false);
@@ -376,6 +386,24 @@ export default function Settings() {
defaultValue={15}
onEditFinished={setGraphLayoutMaxIterations}
/>
+