diff --git a/lightrag_webui/README.md b/lightrag_webui/README.md
index 0a0ac346..ac27f880 100644
--- a/lightrag_webui/README.md
+++ b/lightrag_webui/README.md
@@ -21,7 +21,7 @@ LightRAG WebUI is a React-based web interface for interacting with the LightRAG
Run the following command to build the project:
```bash
- bun run build
+ bun run build --emptyOutDir
```
This command will bundle the project and output the built files to the `lightrag/api/webui` directory.
diff --git a/lightrag_webui/bun.lock b/lightrag_webui/bun.lock
index d0e9a9b4..570fa3a6 100644
--- a/lightrag_webui/bun.lock
+++ b/lightrag_webui/bun.lock
@@ -5,6 +5,7 @@
"name": "lightrag-webui",
"dependencies": {
"@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",
@@ -220,6 +221,8 @@
"@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
+ "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="],
+
"@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
"@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
diff --git a/lightrag_webui/package.json b/lightrag_webui/package.json
index 6d720cc8..26991d9c 100644
--- a/lightrag_webui/package.json
+++ b/lightrag_webui/package.json
@@ -11,6 +11,7 @@
},
"dependencies": {
"@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",
diff --git a/lightrag_webui/src/App.tsx b/lightrag_webui/src/App.tsx
index 1d63b3c1..1eeab2b0 100644
--- a/lightrag_webui/src/App.tsx
+++ b/lightrag_webui/src/App.tsx
@@ -1,13 +1,15 @@
import { useState, useCallback } from 'react'
-import ThemeProvider from '@/components/ThemeProvider'
+import ThemeProvider from '@/components/graph/ThemeProvider'
import MessageAlert from '@/components/MessageAlert'
-import StatusIndicator from '@/components/StatusIndicator'
+import ApiKeyAlert from '@/components/ApiKeyAlert'
+import StatusIndicator from '@/components/graph/StatusIndicator'
import { healthCheckInterval } from '@/lib/constants'
import { useBackendState } from '@/stores/state'
import { useSettingsStore } from '@/stores/settings'
import { useEffect } from 'react'
import { Toaster } from 'sonner'
import SiteHeader from '@/features/SiteHeader'
+import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
import GraphViewer from '@/features/GraphViewer'
import DocumentManager from '@/features/DocumentManager'
@@ -20,6 +22,7 @@ function App() {
const message = useBackendState.use.message()
const enableHealthCheck = useSettingsStore.use.enableHealthCheck()
const [currentTab] = useState(() => useSettingsStore.getState().currentTab)
+ const [apiKeyInvalid, setApiKeyInvalid] = useState(false)
// Health check
useEffect(() => {
@@ -39,6 +42,14 @@ function App() {
[]
)
+ useEffect(() => {
+ if (message) {
+ if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
+ setApiKeyInvalid(true)
+ }
+ }
+ }, [message, setApiKeyInvalid])
+
return (
@@ -64,7 +75,8 @@ function App() {
{enableHealthCheck && }
- {message !== null && }
+ {message !== null && !apiKeyInvalid && }
+ {apiKeyInvalid && }
diff --git a/lightrag_webui/src/components/ApiKeyAlert.tsx b/lightrag_webui/src/components/ApiKeyAlert.tsx
new file mode 100644
index 00000000..f0276085
--- /dev/null
+++ b/lightrag_webui/src/components/ApiKeyAlert.tsx
@@ -0,0 +1,79 @@
+import { useState, useCallback, useEffect } from 'react'
+import {
+ AlertDialog,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger
+} from '@/components/ui/AlertDialog'
+import Button from '@/components/ui/Button'
+import Input from '@/components/ui/Input'
+import { useSettingsStore } from '@/stores/settings'
+import { useBackendState } from '@/stores/state'
+import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
+
+import { toast } from 'sonner'
+
+const ApiKeyAlert = () => {
+ const [opened, setOpened] = useState(true)
+ const apiKey = useSettingsStore.use.apiKey()
+ const [tempApiKey, setTempApiKey] = useState('')
+ const message = useBackendState.use.message()
+
+ useEffect(() => {
+ setTempApiKey(apiKey || '')
+ }, [apiKey, opened])
+
+ useEffect(() => {
+ if (message) {
+ if (message.includes(InvalidApiKeyError) || message.includes(RequireApiKeError)) {
+ setOpened(true)
+ }
+ }
+ }, [message, setOpened])
+
+ const setApiKey = useCallback(async () => {
+ useSettingsStore.setState({ apiKey: tempApiKey || null })
+ if (await useBackendState.getState().check()) {
+ setOpened(false)
+ return
+ }
+ toast.error('API Key is invalid')
+ }, [tempApiKey])
+
+ const handleTempApiKeyChange = useCallback(
+ (e: React.ChangeEvent) => {
+ setTempApiKey(e.target.value)
+ },
+ [setTempApiKey]
+ )
+
+ return (
+
+ Open
+
+
+ API Key is required
+ Please enter your API key
+
+
+
+
+ )
+}
+
+export default ApiKeyAlert
diff --git a/lightrag_webui/src/components/FocusOnNode.tsx b/lightrag_webui/src/components/graph/FocusOnNode.tsx
similarity index 100%
rename from lightrag_webui/src/components/FocusOnNode.tsx
rename to lightrag_webui/src/components/graph/FocusOnNode.tsx
diff --git a/lightrag_webui/src/components/FullScreenControl.tsx b/lightrag_webui/src/components/graph/FullScreenControl.tsx
similarity index 100%
rename from lightrag_webui/src/components/FullScreenControl.tsx
rename to lightrag_webui/src/components/graph/FullScreenControl.tsx
diff --git a/lightrag_webui/src/components/GraphControl.tsx b/lightrag_webui/src/components/graph/GraphControl.tsx
similarity index 100%
rename from lightrag_webui/src/components/GraphControl.tsx
rename to lightrag_webui/src/components/graph/GraphControl.tsx
diff --git a/lightrag_webui/src/components/GraphLabels.tsx b/lightrag_webui/src/components/graph/GraphLabels.tsx
similarity index 100%
rename from lightrag_webui/src/components/GraphLabels.tsx
rename to lightrag_webui/src/components/graph/GraphLabels.tsx
diff --git a/lightrag_webui/src/components/GraphSearch.tsx b/lightrag_webui/src/components/graph/GraphSearch.tsx
similarity index 100%
rename from lightrag_webui/src/components/GraphSearch.tsx
rename to lightrag_webui/src/components/graph/GraphSearch.tsx
diff --git a/lightrag_webui/src/components/LayoutsControl.tsx b/lightrag_webui/src/components/graph/LayoutsControl.tsx
similarity index 100%
rename from lightrag_webui/src/components/LayoutsControl.tsx
rename to lightrag_webui/src/components/graph/LayoutsControl.tsx
diff --git a/lightrag_webui/src/components/PropertiesView.tsx b/lightrag_webui/src/components/graph/PropertiesView.tsx
similarity index 100%
rename from lightrag_webui/src/components/PropertiesView.tsx
rename to lightrag_webui/src/components/graph/PropertiesView.tsx
diff --git a/lightrag_webui/src/components/Settings.tsx b/lightrag_webui/src/components/graph/Settings.tsx
similarity index 98%
rename from lightrag_webui/src/components/Settings.tsx
rename to lightrag_webui/src/components/graph/Settings.tsx
index 2c7dd733..3a6cc51c 100644
--- a/lightrag_webui/src/components/Settings.tsx
+++ b/lightrag_webui/src/components/graph/Settings.tsx
@@ -40,7 +40,7 @@ const LabeledCheckBox = ({
*/
export default function Settings() {
const [opened, setOpened] = useState(false)
- const [tempApiKey, setTempApiKey] = useState('') // 用于临时存储输入的API Key
+ const [tempApiKey, setTempApiKey] = useState('')
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
diff --git a/lightrag_webui/src/components/StatusCard.tsx b/lightrag_webui/src/components/graph/StatusCard.tsx
similarity index 100%
rename from lightrag_webui/src/components/StatusCard.tsx
rename to lightrag_webui/src/components/graph/StatusCard.tsx
diff --git a/lightrag_webui/src/components/StatusIndicator.tsx b/lightrag_webui/src/components/graph/StatusIndicator.tsx
similarity index 96%
rename from lightrag_webui/src/components/StatusIndicator.tsx
rename to lightrag_webui/src/components/graph/StatusIndicator.tsx
index 25d0032c..3272d9fa 100644
--- a/lightrag_webui/src/components/StatusIndicator.tsx
+++ b/lightrag_webui/src/components/graph/StatusIndicator.tsx
@@ -2,7 +2,7 @@ import { cn } from '@/lib/utils'
import { useBackendState } from '@/stores/state'
import { useEffect, useState } from 'react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/Popover'
-import StatusCard from '@/components/StatusCard'
+import StatusCard from '@/components/graph/StatusCard'
const StatusIndicator = () => {
const health = useBackendState.use.health()
diff --git a/lightrag_webui/src/components/ThemeProvider.tsx b/lightrag_webui/src/components/graph/ThemeProvider.tsx
similarity index 100%
rename from lightrag_webui/src/components/ThemeProvider.tsx
rename to lightrag_webui/src/components/graph/ThemeProvider.tsx
diff --git a/lightrag_webui/src/components/ThemeToggle.tsx b/lightrag_webui/src/components/graph/ThemeToggle.tsx
similarity index 100%
rename from lightrag_webui/src/components/ThemeToggle.tsx
rename to lightrag_webui/src/components/graph/ThemeToggle.tsx
diff --git a/lightrag_webui/src/components/ZoomControl.tsx b/lightrag_webui/src/components/graph/ZoomControl.tsx
similarity index 100%
rename from lightrag_webui/src/components/ZoomControl.tsx
rename to lightrag_webui/src/components/graph/ZoomControl.tsx
diff --git a/lightrag_webui/src/components/ui/AlertDialog.tsx b/lightrag_webui/src/components/ui/AlertDialog.tsx
new file mode 100644
index 00000000..36868fcb
--- /dev/null
+++ b/lightrag_webui/src/components/ui/AlertDialog.tsx
@@ -0,0 +1,115 @@
+import * as React from 'react'
+import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
+
+import { cn } from '@/lib/utils'
+import { buttonVariants } from '@/components/ui/Button'
+
+const AlertDialog = AlertDialogPrimitive.Root
+
+const AlertDialogTrigger = AlertDialogPrimitive.Trigger
+
+const AlertDialogPortal = AlertDialogPrimitive.Portal
+
+const AlertDialogOverlay = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
+
+const AlertDialogContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
+
+const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
+
+)
+AlertDialogHeader.displayName = 'AlertDialogHeader'
+
+const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
+
+)
+AlertDialogFooter.displayName = 'AlertDialogFooter'
+
+const AlertDialogTitle = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
+
+const AlertDialogDescription = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
+
+const AlertDialogAction = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
+
+const AlertDialogCancel = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
+
+export {
+ AlertDialog,
+ AlertDialogPortal,
+ AlertDialogOverlay,
+ AlertDialogTrigger,
+ AlertDialogContent,
+ AlertDialogHeader,
+ AlertDialogFooter,
+ AlertDialogTitle,
+ AlertDialogDescription,
+ AlertDialogAction,
+ AlertDialogCancel
+}
diff --git a/lightrag_webui/src/components/ui/Button.tsx b/lightrag_webui/src/components/ui/Button.tsx
index e7e961bc..070e8375 100644
--- a/lightrag_webui/src/components/ui/Button.tsx
+++ b/lightrag_webui/src/components/ui/Button.tsx
@@ -4,7 +4,8 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip'
import { cn } from '@/lib/utils'
-const buttonVariants = cva(
+// eslint-disable-next-line react-refresh/only-export-components
+export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
diff --git a/lightrag_webui/src/features/GraphViewer.tsx b/lightrag_webui/src/features/GraphViewer.tsx
index d5974c41..6d1979c5 100644
--- a/lightrag_webui/src/features/GraphViewer.tsx
+++ b/lightrag_webui/src/features/GraphViewer.tsx
@@ -7,16 +7,16 @@ import { EdgeArrowProgram, NodePointProgram, NodeCircleProgram } from 'sigma/ren
import { NodeBorderProgram } from '@sigma/node-border'
import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve'
-import FocusOnNode from '@/components/FocusOnNode'
-import LayoutsControl from '@/components/LayoutsControl'
-import GraphControl from '@/components/GraphControl'
+import FocusOnNode from '@/components/graph/FocusOnNode'
+import LayoutsControl from '@/components/graph/LayoutsControl'
+import GraphControl from '@/components/graph/GraphControl'
// import ThemeToggle from '@/components/ThemeToggle'
-import ZoomControl from '@/components/ZoomControl'
-import FullScreenControl from '@/components/FullScreenControl'
-import Settings from '@/components/Settings'
-import GraphSearch from '@/components/GraphSearch'
-import GraphLabels from '@/components/GraphLabels'
-import PropertiesView from '@/components/PropertiesView'
+import ZoomControl from '@/components/graph/ZoomControl'
+import FullScreenControl from '@/components/graph/FullScreenControl'
+import Settings from '@/components/graph/Settings'
+import GraphSearch from '@/components/graph/GraphSearch'
+import GraphLabels from '@/components/graph/GraphLabels'
+import PropertiesView from '@/components/graph/PropertiesView'
import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
diff --git a/lightrag_webui/src/features/SiteHeader.tsx b/lightrag_webui/src/features/SiteHeader.tsx
index 6eef0bb9..5446e382 100644
--- a/lightrag_webui/src/features/SiteHeader.tsx
+++ b/lightrag_webui/src/features/SiteHeader.tsx
@@ -1,6 +1,6 @@
import Button from '@/components/ui/Button'
import { SiteInfo } from '@/lib/constants'
-import ThemeToggle from '@/components/ThemeToggle'
+import ThemeToggle from '@/components/graph/ThemeToggle'
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
import { useSettingsStore } from '@/stores/settings'
import { cn } from '@/lib/utils'
diff --git a/lightrag_webui/src/hooks/useTheme.tsx b/lightrag_webui/src/hooks/useTheme.tsx
index 5fb161a4..e1975174 100644
--- a/lightrag_webui/src/hooks/useTheme.tsx
+++ b/lightrag_webui/src/hooks/useTheme.tsx
@@ -1,5 +1,5 @@
import { useContext } from 'react'
-import { ThemeProviderContext } from '@/components/ThemeProvider'
+import { ThemeProviderContext } from '@/components/graph/ThemeProvider'
const useTheme = () => {
const context = useContext(ThemeProviderContext)