From fc3ca2777f1ac6536f334b54af7a5e53d1a72db9 Mon Sep 17 00:00:00 2001 From: ArnoChen Date: Sun, 16 Feb 2025 21:43:14 +0800 Subject: [PATCH] add query settings --- lightrag_webui/bun.lock | 6 + lightrag_webui/index.html | 2 +- lightrag_webui/package.json | 2 + lightrag_webui/src/api/lightrag.ts | 42 ++- lightrag_webui/src/components/Settings.tsx | 2 +- .../ClearDocumentsDialog.tsx | 0 .../UploadDocumentsDialog.tsx | 0 .../components/retrieval/QuerySettings.tsx | 279 ++++++++++++++++++ lightrag_webui/src/components/ui/Checkbox.tsx | 2 +- .../src/components/ui/NumberInput.tsx | 131 ++++++++ lightrag_webui/src/components/ui/Select.tsx | 151 ++++++++++ lightrag_webui/src/components/ui/Tooltip.tsx | 18 +- .../src/features/DocumentManager.tsx | 4 +- .../src/features/RetrievalTesting.tsx | 167 +++++------ lightrag_webui/src/stores/settings.ts | 53 +++- 15 files changed, 757 insertions(+), 102 deletions(-) rename lightrag_webui/src/components/{document => documents}/ClearDocumentsDialog.tsx (100%) rename lightrag_webui/src/components/{document => documents}/UploadDocumentsDialog.tsx (100%) create mode 100644 lightrag_webui/src/components/retrieval/QuerySettings.tsx create mode 100644 lightrag_webui/src/components/ui/NumberInput.tsx create mode 100644 lightrag_webui/src/components/ui/Select.tsx 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 ( +
+ } + placeholder={placeholder} + className="[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" + getInputRef={ref} + {...props} + /> +
+ + +
+
+ ) + } +) + +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 && ( + + )} +
+
+ )) + )} +
+
-
-
- - - setInputValue(e.target.value)} - placeholder="Type your query..." - disabled={isLoading} - /> - -
+
+ + setInputValue(e.target.value)} + placeholder="Type your query..." + disabled={isLoading} + /> + +
+
+
) } + 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 } } )