diff --git a/lightrag_webui/bun.lock b/lightrag_webui/bun.lock
index dc6fda29..86c1f6e2 100644
--- a/lightrag_webui/bun.lock
+++ b/lightrag_webui/bun.lock
@@ -10,6 +10,7 @@
"@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",
@@ -37,6 +38,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
+ "react-number-format": "^5.4.3",
"seedrandom": "^3.0.5",
"sigma": "^3.0.1",
"sonner": "^1.7.4",
@@ -255,6 +257,8 @@
"@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.3", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "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-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ=="],
+ "@radix-ui/react-select": ["@radix-ui/react-select@2.1.6", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "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-T6ajELxRvTuAMWH0YmRJ1qez+x4/7Nq7QIx7zJ0VK3qaEWdnWpNbEDnmWldG1zBDwqrLy5aLMUWcoGirVj5kMg=="],
+
"@radix-ui/react-separator": ["@radix-ui/react-separator@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-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
"@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
@@ -899,6 +903,8 @@
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+ "react-number-format": ["react-number-format@5.4.3", "", { "peerDependencies": { "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ=="],
+
"react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
diff --git a/lightrag_webui/index.html b/lightrag_webui/index.html
index aa36e984..0e019f05 100644
--- a/lightrag_webui/index.html
+++ b/lightrag_webui/index.html
@@ -4,7 +4,7 @@
-
Lightrag Graph Viewer
+ Lightrag
diff --git a/lightrag_webui/package.json b/lightrag_webui/package.json
index 6a9c6778..f69f1394 100644
--- a/lightrag_webui/package.json
+++ b/lightrag_webui/package.json
@@ -16,6 +16,7 @@
"@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",
@@ -43,6 +44,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-dropzone": "^14.3.5",
+ "react-number-format": "^5.4.3",
"seedrandom": "^3.0.5",
"sigma": "^3.0.1",
"sonner": "^1.7.4",
diff --git a/lightrag_webui/src/api/lightrag.ts b/lightrag_webui/src/api/lightrag.ts
index 1a41c765..0f077368 100644
--- a/lightrag_webui/src/api/lightrag.ts
+++ b/lightrag_webui/src/api/lightrag.ts
@@ -52,13 +52,52 @@ export type LightragDocumentsScanProgress = {
progress: number
}
+/**
+ * Specifies the retrieval mode:
+ * - "naive": Performs a basic search without advanced techniques.
+ * - "local": Focuses on context-dependent information.
+ * - "global": Utilizes global knowledge.
+ * - "hybrid": Combines local and global retrieval methods.
+ * - "mix": Integrates knowledge graph and vector retrieval.
+ */
export type QueryMode = 'naive' | 'local' | 'global' | 'hybrid' | 'mix'
+export type Message = {
+ role: 'user' | 'assistant' | 'system'
+ content: string
+}
+
export type QueryRequest = {
query: string
+ /** Specifies the retrieval mode. */
mode: QueryMode
- stream?: boolean
+ /** If True, only returns the retrieved context without generating a response. */
only_need_context?: boolean
+ /** If True, only returns the generated prompt without producing a response. */
+ only_need_prompt?: boolean
+ /** Defines the response format. Examples: 'Multiple Paragraphs', 'Single Paragraph', 'Bullet Points'. */
+ response_type?: string
+ /** If True, enables streaming output for real-time responses. */
+ stream?: boolean
+ /** Number of top items to retrieve. Represents entities in 'local' mode and relationships in 'global' mode. */
+ top_k?: number
+ /** Maximum number of tokens allowed for each retrieved text chunk. */
+ max_token_for_text_unit?: number
+ /** Maximum number of tokens allocated for relationship descriptions in global retrieval. */
+ max_token_for_global_context?: number
+ /** Maximum number of tokens allocated for entity descriptions in local retrieval. */
+ max_token_for_local_context?: number
+ /** List of high-level keywords to prioritize in retrieval. */
+ hl_keywords?: string[]
+ /** List of low-level keywords to refine retrieval focus. */
+ ll_keywords?: string[]
+ /**
+ * Stores past conversation history to maintain context.
+ * Format: [{"role": "user/assistant", "content": "message"}].
+ */
+ conversation_history?: Message[]
+ /** Number of complete conversation turns (user-assistant pairs) to consider in the response context. */
+ history_turns?: number
}
export type QueryResponse = {
@@ -68,7 +107,6 @@ export type QueryResponse = {
export type DocumentActionResponse = {
status: 'success' | 'partial_success' | 'failure'
message: string
- document_count: number
}
export const InvalidApiKeyError = 'Invalid API Key'
diff --git a/lightrag_webui/src/components/Settings.tsx b/lightrag_webui/src/components/Settings.tsx
index 45bc6731..2c7dd733 100644
--- a/lightrag_webui/src/components/Settings.tsx
+++ b/lightrag_webui/src/components/Settings.tsx
@@ -1,5 +1,5 @@
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 Separator from '@/components/ui/Separator'
import Input from '@/components/ui/Input'
diff --git a/lightrag_webui/src/components/document/ClearDocumentsDialog.tsx b/lightrag_webui/src/components/documents/ClearDocumentsDialog.tsx
similarity index 100%
rename from lightrag_webui/src/components/document/ClearDocumentsDialog.tsx
rename to lightrag_webui/src/components/documents/ClearDocumentsDialog.tsx
diff --git a/lightrag_webui/src/components/document/UploadDocumentsDialog.tsx b/lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx
similarity index 100%
rename from lightrag_webui/src/components/document/UploadDocumentsDialog.tsx
rename to lightrag_webui/src/components/documents/UploadDocumentsDialog.tsx
diff --git a/lightrag_webui/src/components/retrieval/QuerySettings.tsx b/lightrag_webui/src/components/retrieval/QuerySettings.tsx
new file mode 100644
index 00000000..3051b33a
--- /dev/null
+++ b/lightrag_webui/src/components/retrieval/QuerySettings.tsx
@@ -0,0 +1,279 @@
+import { useCallback } from 'react'
+import { QueryMode, QueryRequest } from '@/api/lightrag'
+import Text from '@/components/ui/Text'
+import Input from '@/components/ui/Input'
+import Checkbox from '@/components/ui/Checkbox'
+import NumberInput from '@/components/ui/NumberInput'
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card'
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue
+} from '@/components/ui/Select'
+import { useSettingsStore } from '@/stores/settings'
+
+export default function QuerySettings() {
+ const querySettings = useSettingsStore((state) => state.querySettings)
+
+ const handleChange = useCallback((key: keyof QueryRequest, value: any) => {
+ useSettingsStore.getState().updateQuerySettings({ [key]: value })
+ }, [])
+
+ return (
+
+
+ Parameters
+ Configure your query parameters
+
+
+
+
+ {/* Query Mode */}
+ <>
+
+
+ >
+
+ {/* Response Format */}
+ <>
+
+
+ >
+
+ {/* Top K */}
+ <>
+
+
handleChange('top_k', v)}
+ min={1}
+ placeholder="Number of results"
+ />
+ >
+
+ {/* Max Tokens */}
+ <>
+ <>
+
+ handleChange('max_token_for_text_unit', v)}
+ min={1}
+ placeholder="Max tokens for text unit"
+ />
+ >
+
+ <>
+
+ handleChange('max_token_for_global_context', v)}
+ min={1}
+ placeholder="Max tokens for global context"
+ />
+ >
+
+ <>
+
+ handleChange('max_token_for_local_context', v)}
+ min={1}
+ placeholder="Max tokens for local context"
+ />
+ >
+ >
+
+ {/* History Turns */}
+ <>
+
+ handleChange('history_turns', v)}
+ min={0}
+ placeholder="Number of history turns"
+ />
+ >
+
+ {/* Keywords */}
+ <>
+ <>
+
+ {
+ const keywords = e.target.value
+ .split(',')
+ .map((k) => k.trim())
+ .filter((k) => k !== '')
+ handleChange('hl_keywords', keywords)
+ }}
+ placeholder="Enter keywords"
+ />
+ >
+
+ <>
+
+ {
+ const keywords = e.target.value
+ .split(',')
+ .map((k) => k.trim())
+ .filter((k) => k !== '')
+ handleChange('ll_keywords', keywords)
+ }}
+ placeholder="Enter keywords"
+ />
+ >
+ >
+
+ {/* Toggle Options */}
+ <>
+
+
+
+
handleChange('only_need_context', checked)}
+ />
+
+
+
+
+
+
handleChange('only_need_prompt', checked)}
+ />
+
+
+
+
+
+
handleChange('stream', checked)}
+ />
+
+ >
+
+
+
+
+ )
+}
diff --git a/lightrag_webui/src/components/ui/Checkbox.tsx b/lightrag_webui/src/components/ui/Checkbox.tsx
index f11c0b7b..36ebe6e0 100644
--- a/lightrag_webui/src/components/ui/Checkbox.tsx
+++ b/lightrag_webui/src/components/ui/Checkbox.tsx
@@ -23,4 +23,4 @@ const Checkbox = React.forwardRef<
))
Checkbox.displayName = CheckboxPrimitive.Root.displayName
-export { Checkbox }
+export default Checkbox
diff --git a/lightrag_webui/src/components/ui/NumberInput.tsx b/lightrag_webui/src/components/ui/NumberInput.tsx
new file mode 100644
index 00000000..f0c0226c
--- /dev/null
+++ b/lightrag_webui/src/components/ui/NumberInput.tsx
@@ -0,0 +1,131 @@
+import { ChevronDown, ChevronUp } from 'lucide-react'
+import { forwardRef, useCallback, useEffect, useState } from 'react'
+import { NumericFormat, NumericFormatProps } from 'react-number-format'
+import Button from '@/components/ui/Button'
+import Input from '@/components/ui/Input'
+import { cn } from '@/lib/utils'
+
+export interface NumberInputProps extends Omit {
+ stepper?: number
+ thousandSeparator?: string
+ placeholder?: string
+ defaultValue?: number
+ min?: number
+ max?: number
+ value?: number // Controlled value
+ suffix?: string
+ prefix?: string
+ onValueChange?: (value: number | undefined) => void
+ fixedDecimalScale?: boolean
+ decimalScale?: number
+}
+
+const NumberInput = forwardRef(
+ (
+ {
+ stepper,
+ thousandSeparator,
+ placeholder,
+ defaultValue,
+ min = -Infinity,
+ max = Infinity,
+ onValueChange,
+ fixedDecimalScale = false,
+ decimalScale = 0,
+ className = undefined,
+ suffix,
+ prefix,
+ value: controlledValue,
+ ...props
+ },
+ ref
+ ) => {
+ const [value, setValue] = useState(controlledValue ?? defaultValue)
+
+ const handleIncrement = useCallback(() => {
+ setValue((prev) =>
+ prev === undefined ? (stepper ?? 1) : Math.min(prev + (stepper ?? 1), max)
+ )
+ }, [stepper, max])
+
+ const handleDecrement = useCallback(() => {
+ setValue((prev) =>
+ prev === undefined ? -(stepper ?? 1) : Math.max(prev - (stepper ?? 1), min)
+ )
+ }, [stepper, min])
+
+ useEffect(() => {
+ if (controlledValue !== undefined) {
+ setValue(controlledValue)
+ }
+ }, [controlledValue])
+
+ const handleChange = (values: { value: string; floatValue: number | undefined }) => {
+ const newValue = values.floatValue === undefined ? undefined : values.floatValue
+ setValue(newValue)
+ if (onValueChange) {
+ onValueChange(newValue)
+ }
+ }
+
+ const handleBlur = () => {
+ if (value !== undefined) {
+ if (value < min) {
+ setValue(min)
+ ;(ref as React.RefObject).current!.value = String(min)
+ } else if (value > max) {
+ setValue(max)
+ ;(ref as React.RefObject).current!.value = String(max)
+ }
+ }
+ }
+
+ return (
+
+ )
+ }
+)
+
+NumberInput.displayName = 'NumberInput'
+
+export default NumberInput
diff --git a/lightrag_webui/src/components/ui/Select.tsx b/lightrag_webui/src/components/ui/Select.tsx
new file mode 100644
index 00000000..cd814948
--- /dev/null
+++ b/lightrag_webui/src/components/ui/Select.tsx
@@ -0,0 +1,151 @@
+import * as React from 'react'
+import * as SelectPrimitive from '@radix-ui/react-select'
+import { Check, ChevronDown, ChevronUp } from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+
+const Select = SelectPrimitive.Root
+
+const SelectGroup = SelectPrimitive.Group
+
+const SelectValue = SelectPrimitive.Value
+
+const SelectTrigger = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+ span]:line-clamp-1',
+ className
+ )}
+ {...props}
+ >
+ {children}
+
+
+
+
+))
+SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
+
+const SelectScrollUpButton = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
+
+const SelectScrollDownButton = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
+
+const SelectContent = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, position = 'popper', ...props }, ref) => (
+
+
+
+
+ {children}
+
+
+
+
+))
+SelectContent.displayName = SelectPrimitive.Content.displayName
+
+const SelectLabel = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectLabel.displayName = SelectPrimitive.Label.displayName
+
+const SelectItem = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+
+ {children}
+
+))
+SelectItem.displayName = SelectPrimitive.Item.displayName
+
+const SelectSeparator = React.forwardRef<
+ React.ComponentRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SelectSeparator.displayName = SelectPrimitive.Separator.displayName
+
+export {
+ Select,
+ SelectGroup,
+ SelectValue,
+ SelectTrigger,
+ SelectContent,
+ SelectLabel,
+ SelectItem,
+ SelectSeparator,
+ SelectScrollUpButton,
+ SelectScrollDownButton
+}
diff --git a/lightrag_webui/src/components/ui/Tooltip.tsx b/lightrag_webui/src/components/ui/Tooltip.tsx
index 3d643dbc..674ddd50 100644
--- a/lightrag_webui/src/components/ui/Tooltip.tsx
+++ b/lightrag_webui/src/components/ui/Tooltip.tsx
@@ -8,19 +8,31 @@ const Tooltip = TooltipPrimitive.Root
const TooltipTrigger = TooltipPrimitive.Trigger
+const processTooltipContent = (content: string) => {
+ if (typeof content !== 'string') return content
+ return content.split('\\n').map((line, i) => (
+
+ {line}
+ {i < content.split('\\n').length - 1 &&
}
+
+ ))
+}
+
const TooltipContent = React.forwardRef<
React.ComponentRef,
React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
+>(({ className, sideOffset = 4, children, ...props }, ref) => (
+ >
+ {typeof children === 'string' ? processTooltipContent(children) : children}
+
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx
index 2473e036..40185a37 100644
--- a/lightrag_webui/src/features/DocumentManager.tsx
+++ b/lightrag_webui/src/features/DocumentManager.tsx
@@ -11,8 +11,8 @@ import {
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from '@/components/ui/Card'
import Progress from '@/components/ui/Progress'
import EmptyCard from '@/components/ui/EmptyCard'
-import UploadDocumentsDialog from '@/components/document/UploadDocumentsDialog'
-import ClearDocumentsDialog from '@/components/document/ClearDocumentsDialog'
+import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
+import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
import {
getDocuments,
diff --git a/lightrag_webui/src/features/RetrievalTesting.tsx b/lightrag_webui/src/features/RetrievalTesting.tsx
index 2ed7c2a4..cbad51c9 100644
--- a/lightrag_webui/src/features/RetrievalTesting.tsx
+++ b/lightrag_webui/src/features/RetrievalTesting.tsx
@@ -1,17 +1,13 @@
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 { queryText, queryTextStream, Message } 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'
+import QuerySettings from '@/components/retrieval/QuerySettings'
-type Message = {
- id: string
- content: string
- role: 'User' | 'LightRAG'
-}
+import { EraserIcon, SendIcon, LoaderIcon } from 'lucide-react'
export default function RetrievalTesting() {
const [messages, setMessages] = useState(
@@ -19,7 +15,6 @@ export default function RetrievalTesting() {
)
const [inputValue, setInputValue] = useState('')
const [isLoading, setIsLoading] = useState(false)
- const [mode, setMode] = useState('mix')
const messagesEndRef = useRef(null)
const scrollToBottom = useCallback(() => {
@@ -31,23 +26,24 @@ export default function RetrievalTesting() {
e.preventDefault()
if (!inputValue.trim() || isLoading) return
+ // Create messages
const userMessage: Message = {
- id: Date.now().toString(),
content: inputValue,
- role: 'User'
+ role: 'user'
}
const assistantMessage: Message = {
- id: (Date.now() + 1).toString(),
content: '',
- role: 'LightRAG'
+ role: 'assistant'
}
+ // Add messages to chatbox
setMessages((prev) => {
const newMessages = [...prev, userMessage, assistantMessage]
return newMessages
})
+ // Clear input and set loading
setInputValue('')
setIsLoading(true)
@@ -57,25 +53,34 @@ export default function RetrievalTesting() {
setMessages((prev) => {
const newMessages = [...prev]
const lastMessage = newMessages[newMessages.length - 1]
- if (lastMessage.role === 'LightRAG') {
+ if (lastMessage.role === 'assistant') {
lastMessage.content = assistantMessage.content
}
return newMessages
})
}
+ // Prepare query parameters
+ const state = useSettingsStore.getState()
+ const queryParams = {
+ ...state.querySettings,
+ query: userMessage.content,
+ conversation_history: messages
+ }
+
try {
- await queryTextStream(
- {
- query: userMessage.content,
- mode: mode,
- stream: true
- },
- updateAssistantMessage
- )
+ // Run query
+ if (state.querySettings.stream) {
+ await queryTextStream(queryParams, updateAssistantMessage)
+ } else {
+ const response = await queryText(queryParams)
+ updateAssistantMessage(response.response)
+ }
} catch (err) {
+ // Handle error
updateAssistantMessage(`Error: Failed to get response\n${errorMessage(err)}`)
} finally {
+ // Clear loading and add messages to state
setIsLoading(false)
useSettingsStore
.getState()
@@ -86,7 +91,7 @@ export default function RetrievalTesting() {
])
}
},
- [inputValue, isLoading, mode, setMessages]
+ [inputValue, isLoading, messages, setMessages]
)
const debouncedMessages = useDebounce(messages, 100)
@@ -98,73 +103,65 @@ export default function RetrievalTesting() {
}, [setMessages])
return (
-
-
-
-
- {messages.length === 0 ? (
-
- Start a retrieval by typing your query below
-
- ) : (
- messages.map((message) => (
-
-
-
{message.content}
- {message.content.length === 0 && (
-
- )}
-
+
+
+
+
+
+ {messages.length === 0 ? (
+
+ Start a retrieval by typing your query below
- ))
- )}
-
+ ) : (
+ messages.map((message, idx) => (
+
+
+
{message.content}
+ {message.content.length === 0 && (
+
+ )}
+
+
+ ))
+ )}
+
+
-
-
+
+
+
)
}
+
diff --git a/lightrag_webui/src/stores/settings.ts b/lightrag_webui/src/stores/settings.ts
index af8e3a95..84f5feac 100644
--- a/lightrag_webui/src/stores/settings.ts
+++ b/lightrag_webui/src/stores/settings.ts
@@ -2,6 +2,7 @@ import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import { createSelectors } from '@/lib/utils'
import { defaultQueryLabel } from '@/lib/constants'
+import { Message, QueryRequest } from '@/api/lightrag'
type Theme = 'dark' | 'light' | 'system'
type Tab = 'documents' | 'knowledge-graph' | 'retrieval'
@@ -32,8 +33,11 @@ interface SettingsState {
currentTab: Tab
setCurrentTab: (tab: Tab) => void
- retrievalHistory: any[]
- setRetrievalHistory: (history: any[]) => void
+ retrievalHistory: Message[]
+ setRetrievalHistory: (history: Message[]) => void
+
+ querySettings: Omit
+ updateQuerySettings: (settings: Partial) => void
}
const useSettingsStoreBase = create()(
@@ -60,6 +64,21 @@ const useSettingsStoreBase = create()(
retrievalHistory: [],
+ querySettings: {
+ mode: 'global',
+ response_type: 'Multiple Paragraphs',
+ top_k: 10,
+ max_token_for_text_unit: 4000,
+ max_token_for_global_context: 4000,
+ max_token_for_local_context: 4000,
+ only_need_context: false,
+ only_need_prompt: false,
+ stream: true,
+ history_turns: 3,
+ hl_keywords: [],
+ ll_keywords: []
+ },
+
setTheme: (theme: Theme) => set({ theme }),
setQueryLabel: (queryLabel: string) =>
@@ -73,15 +92,17 @@ const useSettingsStoreBase = create()(
setCurrentTab: (tab: Tab) => set({ currentTab: tab }),
- setRetrievalHistory: (history: any[]) => set({ retrievalHistory: history })
+ setRetrievalHistory: (history: Message[]) => set({ retrievalHistory: history }),
+
+ updateQuerySettings: (settings: Partial) =>
+ set((state) => ({
+ querySettings: { ...state.querySettings, ...settings }
+ }))
}),
{
name: 'settings-storage',
storage: createJSONStorage(() => localStorage),
- version: 5,
- partialize(state) {
- return { ...state, retrievalHistory: undefined }
- },
+ version: 6,
migrate: (state: any, version: number) => {
if (version < 2) {
state.showEdgeLabel = false
@@ -99,6 +120,24 @@ const useSettingsStoreBase = create()(
if (version < 5) {
state.currentTab = 'documents'
}
+ if (version < 6) {
+ state.querySettings = {
+ mode: 'global',
+ response_type: 'Multiple Paragraphs',
+ top_k: 10,
+ max_token_for_text_unit: 4000,
+ max_token_for_global_context: 4000,
+ max_token_for_local_context: 4000,
+ only_need_context: false,
+ only_need_prompt: false,
+ stream: true,
+ history_turns: 3,
+ hl_keywords: [],
+ ll_keywords: []
+ }
+ state.retrievalHistory = []
+ }
+ return state
}
}
)