Merge branch 'main' into limit-vdb-metadata-size
This commit is contained in:
commit
03333d63f3
11 changed files with 102 additions and 59 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -9,9 +9,10 @@ __pycache__/
|
||||||
|
|
||||||
# Virtual Environment
|
# Virtual Environment
|
||||||
.venv/
|
.venv/
|
||||||
env/
|
|
||||||
venv/
|
venv/
|
||||||
*.env*
|
|
||||||
|
# Enviroment Variable Files
|
||||||
|
.env
|
||||||
|
|
||||||
# Build / Distribution
|
# Build / Distribution
|
||||||
dist/
|
dist/
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__api_version__ = "0240"
|
__api_version__ = "0241"
|
||||||
|
|
|
||||||
|
|
@ -345,14 +345,17 @@ def create_app(args):
|
||||||
finalize_share_data()
|
finalize_share_data()
|
||||||
|
|
||||||
# Initialize FastAPI
|
# Initialize FastAPI
|
||||||
|
base_description = (
|
||||||
|
"Providing API for LightRAG core, Web UI and Ollama Model Emulation"
|
||||||
|
)
|
||||||
|
swagger_description = (
|
||||||
|
base_description
|
||||||
|
+ (" (With authentication)" if api_key else "")
|
||||||
|
+ "\n\n[View ReDoc documentation](/redoc)"
|
||||||
|
)
|
||||||
app_kwargs = {
|
app_kwargs = {
|
||||||
"title": "LightRAG Server API",
|
"title": "LightRAG Server API",
|
||||||
"description": (
|
"description": swagger_description,
|
||||||
"Providing API for LightRAG core, Web UI and Ollama Model Emulation"
|
|
||||||
+ "(With authentication)"
|
|
||||||
if api_key
|
|
||||||
else ""
|
|
||||||
),
|
|
||||||
"version": __api_version__,
|
"version": __api_version__,
|
||||||
"openapi_url": "/openapi.json", # Explicitly set OpenAPI schema URL
|
"openapi_url": "/openapi.json", # Explicitly set OpenAPI schema URL
|
||||||
"docs_url": "/docs", # Explicitly set docs URL
|
"docs_url": "/docs", # Explicitly set docs URL
|
||||||
|
|
|
||||||
|
|
@ -2629,9 +2629,9 @@ def fix_tuple_delimiter_corruption(
|
||||||
record,
|
record,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fix: <X|#|> -> <|#|>, <|#|Y> -> <|#|>, <X|#|Y> -> <|#|>, <||#||> -> <|#|>, <||#> -> <|#|> (one extra characters outside pipes)
|
# Fix: <X|#|> -> <|#|>, <|#|Y> -> <|#|>, <X|#|Y> -> <|#|>, <||#||> -> <|#|> (one extra characters outside pipes)
|
||||||
record = re.sub(
|
record = re.sub(
|
||||||
rf"<.?\|{escaped_delimiter_core}\|*?>",
|
rf"<.?\|{escaped_delimiter_core}\|.?>",
|
||||||
tuple_delimiter,
|
tuple_delimiter,
|
||||||
record,
|
record,
|
||||||
)
|
)
|
||||||
|
|
@ -2651,7 +2651,6 @@ def fix_tuple_delimiter_corruption(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fix: <|#| -> <|#|>, <|#|| -> <|#|> (missing closing >)
|
# Fix: <|#| -> <|#|>, <|#|| -> <|#|> (missing closing >)
|
||||||
|
|
||||||
record = re.sub(
|
record = re.sub(
|
||||||
rf"<\|{escaped_delimiter_core}\|+(?!>)",
|
rf"<\|{escaped_delimiter_core}\|+(?!>)",
|
||||||
tuple_delimiter,
|
tuple_delimiter,
|
||||||
|
|
@ -2665,6 +2664,13 @@ def fix_tuple_delimiter_corruption(
|
||||||
record,
|
record,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Fix: <||#> -> <|#|> (double pipe at start, missing pipe at end)
|
||||||
|
record = re.sub(
|
||||||
|
rf"<\|+{escaped_delimiter_core}>",
|
||||||
|
tuple_delimiter,
|
||||||
|
record,
|
||||||
|
)
|
||||||
|
|
||||||
# Fix: <|| -> <|#|>
|
# Fix: <|| -> <|#|>
|
||||||
record = re.sub(
|
record = re.sub(
|
||||||
r"<\|\|(?!>)",
|
r"<\|\|(?!>)",
|
||||||
|
|
|
||||||
4
lightrag_webui/.env.development
Normal file
4
lightrag_webui/.env.development
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Development environment configuration
|
||||||
|
VITE_BACKEND_URL=http://localhost:9621
|
||||||
|
VITE_API_PROXY=true
|
||||||
|
VITE_API_ENDPOINTS=/api,/documents,/graphs,/graph,/health,/query,/docs,/redoc,/openapi.json,/login,/auth-status
|
||||||
|
|
@ -1,2 +1,4 @@
|
||||||
# Development environment configuration
|
# Development environment configuration
|
||||||
VITE_BACKEND_URL=/api
|
VITE_BACKEND_URL=http://localhost:9621
|
||||||
|
VITE_API_PROXY=true
|
||||||
|
VITE_API_ENDPOINTS=/api,/documents,/graphs,/graph,/health,/query,/docs,/redoc,/openapi.json,/login,/auth-status
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
VITE_BACKEND_URL=http://localhost:9621
|
VITE_BACKEND_URL=http://localhost:9621
|
||||||
VITE_API_PROXY=true
|
VITE_API_PROXY=true
|
||||||
VITE_API_ENDPOINTS=/,/api,/documents,/graphs,/graph,/health,/query,/docs,/openapi.json,/login,/auth-status
|
VITE_API_ENDPOINTS=/api,/documents,/graphs,/graph,/health,/query,/docs,/redoc,/openapi.json,/login,/auth-status
|
||||||
|
|
|
||||||
|
|
@ -175,49 +175,59 @@ const GraphLabels = () => {
|
||||||
>
|
>
|
||||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
<AsyncSelect<string>
|
<div className="w-full min-w-[280px] max-w-[500px]">
|
||||||
key={selectKey} // Force re-render when data changes
|
<AsyncSelect<string>
|
||||||
className="min-w-[300px]"
|
key={selectKey} // Force re-render when data changes
|
||||||
triggerClassName="max-h-8"
|
className="min-w-[300px]"
|
||||||
searchInputClassName="max-h-8"
|
triggerClassName="max-h-8 w-full overflow-hidden"
|
||||||
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
|
searchInputClassName="max-h-8"
|
||||||
fetcher={fetchData}
|
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
|
||||||
renderOption={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
|
fetcher={fetchData}
|
||||||
getOptionValue={(item) => item}
|
renderOption={(item) => (
|
||||||
getDisplayValue={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
|
<div className="truncate" title={item}>
|
||||||
notFound={<div className="py-6 text-center text-sm">{t('graphPanel.graphLabels.noLabels')}</div>}
|
{item}
|
||||||
ariaLabel={t('graphPanel.graphLabels.label')}
|
</div>
|
||||||
placeholder={t('graphPanel.graphLabels.placeholder')}
|
)}
|
||||||
searchPlaceholder={t('graphPanel.graphLabels.placeholder')}
|
getOptionValue={(item) => item}
|
||||||
noResultsMessage={t('graphPanel.graphLabels.noLabels')}
|
getDisplayValue={(item) => (
|
||||||
value={label !== null ? label : '*'}
|
<div className="min-w-0 flex-1 truncate text-left" title={item}>
|
||||||
onChange={(newLabel) => {
|
{item}
|
||||||
const currentLabel = useSettingsStore.getState().queryLabel;
|
</div>
|
||||||
|
)}
|
||||||
|
notFound={<div className="py-6 text-center text-sm">{t('graphPanel.graphLabels.noLabels')}</div>}
|
||||||
|
ariaLabel={t('graphPanel.graphLabels.label')}
|
||||||
|
placeholder={t('graphPanel.graphLabels.placeholder')}
|
||||||
|
searchPlaceholder={t('graphPanel.graphLabels.placeholder')}
|
||||||
|
noResultsMessage={t('graphPanel.graphLabels.noLabels')}
|
||||||
|
value={label !== null ? label : '*'}
|
||||||
|
onChange={(newLabel) => {
|
||||||
|
const currentLabel = useSettingsStore.getState().queryLabel;
|
||||||
|
|
||||||
// select the last item means query all
|
// select the last item means query all
|
||||||
if (newLabel === '...') {
|
if (newLabel === '...') {
|
||||||
newLabel = '*';
|
newLabel = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle reselecting the same label
|
// Handle reselecting the same label
|
||||||
if (newLabel === currentLabel && newLabel !== '*') {
|
if (newLabel === currentLabel && newLabel !== '*') {
|
||||||
newLabel = '*';
|
newLabel = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add selected label to search history (except for special cases)
|
// Add selected label to search history (except for special cases)
|
||||||
if (newLabel && newLabel !== '*' && newLabel !== '...' && newLabel.trim() !== '') {
|
if (newLabel && newLabel !== '*' && newLabel !== '...' && newLabel.trim() !== '') {
|
||||||
SearchHistoryManager.addToHistory(newLabel);
|
SearchHistoryManager.addToHistory(newLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset graphDataFetchAttempted flag to ensure data fetch is triggered
|
// Reset graphDataFetchAttempted flag to ensure data fetch is triggered
|
||||||
useGraphStore.getState().setGraphDataFetchAttempted(false);
|
useGraphStore.getState().setGraphDataFetchAttempted(false);
|
||||||
|
|
||||||
// Update the label to trigger data loading
|
// Update the label to trigger data loading
|
||||||
useSettingsStore.getState().setQueryLabel(newLabel);
|
useSettingsStore.getState().setQueryLabel(newLabel);
|
||||||
}}
|
}}
|
||||||
clearable={false} // Prevent clearing value on reselect
|
clearable={false} // Prevent clearing value on reselect
|
||||||
debounceTime={500}
|
debounceTime={500}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,13 @@ export type MessageWithError = Message & {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore original component definition and export
|
// Restore original component definition and export
|
||||||
export const ChatMessage = ({ message }: { message: MessageWithError }) => { // Remove isComplete prop
|
export const ChatMessage = ({
|
||||||
|
message,
|
||||||
|
isTabActive = true
|
||||||
|
}: {
|
||||||
|
message: MessageWithError
|
||||||
|
isTabActive?: boolean
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [katexPlugin, setKatexPlugin] = useState<((options?: KaTeXOptions) => any) | null>(null)
|
const [katexPlugin, setKatexPlugin] = useState<((options?: KaTeXOptions) => any) | null>(null)
|
||||||
|
|
@ -148,8 +154,13 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
|
||||||
} rounded-lg px-4 py-2`}
|
} rounded-lg px-4 py-2`}
|
||||||
>
|
>
|
||||||
{/* Thinking process display - only for assistant messages */}
|
{/* Thinking process display - only for assistant messages */}
|
||||||
|
{/* Always render to prevent layout shift when switching tabs */}
|
||||||
{message.role === 'assistant' && (isThinking || thinkingTime !== null) && (
|
{message.role === 'assistant' && (isThinking || thinkingTime !== null) && (
|
||||||
<div className="mb-2">
|
<div className={cn(
|
||||||
|
'mb-2',
|
||||||
|
// Reduce visual priority in inactive tabs while maintaining layout
|
||||||
|
!isTabActive && 'opacity-50'
|
||||||
|
)}>
|
||||||
<div
|
<div
|
||||||
className="flex items-center text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors duration-200 text-sm cursor-pointer select-none"
|
className="flex items-center text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors duration-200 text-sm cursor-pointer select-none"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -161,7 +172,8 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
|
||||||
>
|
>
|
||||||
{isThinking ? (
|
{isThinking ? (
|
||||||
<>
|
<>
|
||||||
<LoaderIcon className="mr-2 size-4 animate-spin" />
|
{/* Only show spinner animation in active tab to save resources */}
|
||||||
|
{isTabActive && <LoaderIcon className="mr-2 size-4 animate-spin" />}
|
||||||
<span>{t('retrievePanel.chatMessage.thinking')}</span>
|
<span>{t('retrievePanel.chatMessage.thinking')}</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -247,11 +259,12 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(() => {
|
{/* Loading indicator - only show in active tab */}
|
||||||
|
{isTabActive && (() => {
|
||||||
// More comprehensive loading state check
|
// More comprehensive loading state check
|
||||||
const hasVisibleContent = finalDisplayContent && finalDisplayContent.trim() !== '';
|
const hasVisibleContent = finalDisplayContent && finalDisplayContent.trim() !== '';
|
||||||
const isLoadingState = !hasVisibleContent && !isThinking && !thinkingTime;
|
const isLoadingState = !hasVisibleContent && !isThinking && !thinkingTime;
|
||||||
return isLoadingState && <LoaderIcon className="animate-spin duration-2000" />;
|
return isLoadingState && <LoaderIcon className="animate-spin duration-2000" />
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,10 @@ const parseCOTContent = (content: string) => {
|
||||||
|
|
||||||
export default function RetrievalTesting() {
|
export default function RetrievalTesting() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
// Get current tab to determine if this tab is active (for performance optimization)
|
||||||
|
const currentTab = useSettingsStore.use.currentTab()
|
||||||
|
const isRetrievalTabActive = currentTab === 'retrieval'
|
||||||
|
|
||||||
const [messages, setMessages] = useState<MessageWithError[]>(() => {
|
const [messages, setMessages] = useState<MessageWithError[]>(() => {
|
||||||
try {
|
try {
|
||||||
const history = useSettingsStore.getState().retrievalHistory || []
|
const history = useSettingsStore.getState().retrievalHistory || []
|
||||||
|
|
@ -716,7 +720,7 @@ export default function RetrievalTesting() {
|
||||||
<CopyIcon className="size-4" />
|
<CopyIcon className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<ChatMessage message={message} />
|
<ChatMessage message={message} isTabActive={isRetrievalTabActive} />
|
||||||
{message.role === 'assistant' && (
|
{message.role === 'assistant' && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleCopyMessage(message)}
|
onClick={() => handleCopyMessage(message)}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ export default defineConfig({
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: endpoint === '/api' ?
|
rewrite: endpoint === '/api' ?
|
||||||
(path) => path.replace(/^\/api/, '') :
|
(path) => path.replace(/^\/api/, '') :
|
||||||
endpoint === '/docs' || endpoint === '/openapi.json' ?
|
endpoint === '/docs' || endpoint === '/redoc' || endpoint === '/openapi.json' ?
|
||||||
(path) => path : undefined
|
(path) => path : undefined
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue