+// eslint-disable-next-line react/prop-types
>(({ className, ...props }, ref) => (
+ // eslint-disable-next-line react/prop-types
>(({ className, ...props }, ref) => (
| ([])
const [indexedFiles, setIndexedFiles] = useState([])
- const [scanProgress, setScanProgress] = useState(null)
+ // const [scanProgress, setScanProgress] = useState(null)
const fetchDocuments = useCallback(async () => {
try {
@@ -45,7 +45,7 @@ export default function DocumentManager() {
useEffect(() => {
fetchDocuments()
- }, [])
+ }, []) // eslint-disable-line react-hooks/exhaustive-deps
const scanDocuments = useCallback(async () => {
try {
@@ -54,26 +54,26 @@ export default function DocumentManager() {
} catch (err) {
toast.error('Failed to load documents\n' + errorMessage(err))
}
- }, [setFiles])
+ }, [])
- useEffect(() => {
- const interval = setInterval(async () => {
- try {
- if (!health) return
- const progress = await getDocumentsScanProgress()
- setScanProgress((pre) => {
- if (pre?.is_scanning === progress.is_scanning && progress.is_scanning === false) {
- return pre
- }
- return progress
- })
- console.log(progress)
- } catch (err) {
- toast.error('Failed to get scan progress\n' + errorMessage(err))
- }
- }, 2000)
- return () => clearInterval(interval)
- }, [health])
+ // useEffect(() => {
+ // const interval = setInterval(async () => {
+ // try {
+ // if (!health) return
+ // const progress = await getDocumentsScanProgress()
+ // setScanProgress((pre) => {
+ // if (pre?.is_scanning === progress.is_scanning && progress.is_scanning === false) {
+ // return pre
+ // }
+ // return progress
+ // })
+ // console.log(progress)
+ // } catch (err) {
+ // toast.error('Failed to get scan progress\n' + errorMessage(err))
+ // }
+ // }, 2000)
+ // return () => clearInterval(interval)
+ // }, [health])
const handleDelete = async (fileName: string) => {
console.log(`deleting ${fileName}`)
@@ -88,19 +88,19 @@ export default function DocumentManager() {
- {scanProgress?.is_scanning && (
+ {/* {scanProgress?.is_scanning && (
Indexing {scanProgress.current_file}
@@ -108,7 +108,7 @@ export default function DocumentManager() {
- )}
+ )} */}
diff --git a/lightrag_webui/src/features/RetrievalTesting.tsx b/lightrag_webui/src/features/RetrievalTesting.tsx
new file mode 100644
index 00000000..2ed7c2a4
--- /dev/null
+++ b/lightrag_webui/src/features/RetrievalTesting.tsx
@@ -0,0 +1,170 @@
+import Input from '@/components/ui/Input'
+import Button from '@/components/ui/Button'
+import { useCallback, useEffect, useRef, useState } from 'react'
+import { queryTextStream, QueryMode } from '@/api/lightrag'
+import { errorMessage } from '@/lib/utils'
+import { useSettingsStore } from '@/stores/settings'
+import { useDebounce } from '@/hooks/useDebounce'
+import { EraserIcon, SendIcon, LoaderIcon } from 'lucide-react'
+
+type Message = {
+ id: string
+ content: string
+ role: 'User' | 'LightRAG'
+}
+
+export default function RetrievalTesting() {
+ const [messages, setMessages] = useState(
+ () => useSettingsStore.getState().retrievalHistory || []
+ )
+ const [inputValue, setInputValue] = useState('')
+ const [isLoading, setIsLoading] = useState(false)
+ const [mode, setMode] = useState('mix')
+ const messagesEndRef = useRef(null)
+
+ const scrollToBottom = useCallback(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
+ }, [])
+
+ const handleSubmit = useCallback(
+ async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!inputValue.trim() || isLoading) return
+
+ const userMessage: Message = {
+ id: Date.now().toString(),
+ content: inputValue,
+ role: 'User'
+ }
+
+ const assistantMessage: Message = {
+ id: (Date.now() + 1).toString(),
+ content: '',
+ role: 'LightRAG'
+ }
+
+ setMessages((prev) => {
+ const newMessages = [...prev, userMessage, assistantMessage]
+ return newMessages
+ })
+
+ setInputValue('')
+ setIsLoading(true)
+
+ // Create a function to update the assistant's message
+ const updateAssistantMessage = (chunk: string) => {
+ assistantMessage.content += chunk
+ setMessages((prev) => {
+ const newMessages = [...prev]
+ const lastMessage = newMessages[newMessages.length - 1]
+ if (lastMessage.role === 'LightRAG') {
+ lastMessage.content = assistantMessage.content
+ }
+ return newMessages
+ })
+ }
+
+ try {
+ await queryTextStream(
+ {
+ query: userMessage.content,
+ mode: mode,
+ stream: true
+ },
+ updateAssistantMessage
+ )
+ } catch (err) {
+ updateAssistantMessage(`Error: Failed to get response\n${errorMessage(err)}`)
+ } finally {
+ setIsLoading(false)
+ useSettingsStore
+ .getState()
+ .setRetrievalHistory([
+ ...useSettingsStore.getState().retrievalHistory,
+ userMessage,
+ assistantMessage
+ ])
+ }
+ },
+ [inputValue, isLoading, mode, setMessages]
+ )
+
+ const debouncedMessages = useDebounce(messages, 100)
+ useEffect(() => scrollToBottom(), [debouncedMessages, scrollToBottom])
+
+ const clearMessages = useCallback(() => {
+ setMessages([])
+ useSettingsStore.getState().setRetrievalHistory([])
+ }, [setMessages])
+
+ return (
+
+
+
+
+ {messages.length === 0 ? (
+
+ Start a retrieval by typing your query below
+
+ ) : (
+ messages.map((message) => (
+
+
+ {message.content}
+ {message.content.length === 0 && (
+
+ )}
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+ )
+}
diff --git a/lightrag_webui/src/features/SiteHeader.tsx b/lightrag_webui/src/features/SiteHeader.tsx
index ef54882e..e1ade6cf 100644
--- a/lightrag_webui/src/features/SiteHeader.tsx
+++ b/lightrag_webui/src/features/SiteHeader.tsx
@@ -2,14 +2,18 @@ import Button from '@/components/ui/Button'
import { SiteInfo } from '@/lib/constants'
import ThemeToggle from '@/components/ThemeToggle'
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
+import { useSettingsStore } from '@/stores/settings'
+import { cn } from '@/lib/utils'
import { ZapIcon, GithubIcon } from 'lucide-react'
export default function SiteHeader() {
+ const currentTab = useSettingsStore.use.currentTab()
+
return (
-
+
{SiteInfo.name}
@@ -18,28 +22,43 @@ export default function SiteHeader() {
Documents
Knowledge Graph
- {/*
- Settings
- */}
+ Retrieval
+
|