Merge branch 'main' into limit-vdb-metadata-size

This commit is contained in:
yangdx 2025-10-17 21:36:06 +08:00
commit 03333d63f3
11 changed files with 102 additions and 59 deletions

5
.gitignore vendored
View file

@ -9,9 +9,10 @@ __pycache__/
# Virtual Environment
.venv/
env/
venv/
*.env*
# Enviroment Variable Files
.env
# Build / Distribution
dist/

View file

@ -1 +1 @@
__api_version__ = "0240"
__api_version__ = "0241"

View file

@ -345,14 +345,17 @@ def create_app(args):
finalize_share_data()
# 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 = {
"title": "LightRAG Server API",
"description": (
"Providing API for LightRAG core, Web UI and Ollama Model Emulation"
+ "(With authentication)"
if api_key
else ""
),
"description": swagger_description,
"version": __api_version__,
"openapi_url": "/openapi.json", # Explicitly set OpenAPI schema URL
"docs_url": "/docs", # Explicitly set docs URL

View file

@ -2629,9 +2629,9 @@ def fix_tuple_delimiter_corruption(
record,
)
# Fix: <X|#|> -> <|#|>, <|#|Y> -> <|#|>, <X|#|Y> -> <|#|>, <||#||> -> <|#|>, <||#> -> <|#|> (one extra characters outside pipes)
# Fix: <X|#|> -> <|#|>, <|#|Y> -> <|#|>, <X|#|Y> -> <|#|>, <||#||> -> <|#|> (one extra characters outside pipes)
record = re.sub(
rf"<.?\|{escaped_delimiter_core}\|*?>",
rf"<.?\|{escaped_delimiter_core}\|.?>",
tuple_delimiter,
record,
)
@ -2651,7 +2651,6 @@ def fix_tuple_delimiter_corruption(
)
# Fix: <|#| -> <|#|>, <|#|| -> <|#|> (missing closing >)
record = re.sub(
rf"<\|{escaped_delimiter_core}\|+(?!>)",
tuple_delimiter,
@ -2665,6 +2664,13 @@ def fix_tuple_delimiter_corruption(
record,
)
# Fix: <||#> -> <|#|> (double pipe at start, missing pipe at end)
record = re.sub(
rf"<\|+{escaped_delimiter_core}>",
tuple_delimiter,
record,
)
# Fix: <|| -> <|#|>
record = re.sub(
r"<\|\|(?!>)",

View 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

View file

@ -1,2 +1,4 @@
# 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

View file

@ -1,3 +1,3 @@
VITE_BACKEND_URL=http://localhost:9621
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

View file

@ -175,49 +175,59 @@ const GraphLabels = () => {
>
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
</Button>
<AsyncSelect<string>
key={selectKey} // Force re-render when data changes
className="min-w-[300px]"
triggerClassName="max-h-8"
searchInputClassName="max-h-8"
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
fetcher={fetchData}
renderOption={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</div>}
getOptionValue={(item) => item}
getDisplayValue={(item) => <div style={{ whiteSpace: 'pre' }}>{item}</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;
<div className="w-full min-w-[280px] max-w-[500px]">
<AsyncSelect<string>
key={selectKey} // Force re-render when data changes
className="min-w-[300px]"
triggerClassName="max-h-8 w-full overflow-hidden"
searchInputClassName="max-h-8"
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
fetcher={fetchData}
renderOption={(item) => (
<div className="truncate" title={item}>
{item}
</div>
)}
getOptionValue={(item) => item}
getDisplayValue={(item) => (
<div className="min-w-0 flex-1 truncate text-left" title={item}>
{item}
</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
if (newLabel === '...') {
newLabel = '*';
}
// select the last item means query all
if (newLabel === '...') {
newLabel = '*';
}
// Handle reselecting the same label
if (newLabel === currentLabel && newLabel !== '*') {
newLabel = '*';
}
// Handle reselecting the same label
if (newLabel === currentLabel && newLabel !== '*') {
newLabel = '*';
}
// Add selected label to search history (except for special cases)
if (newLabel && newLabel !== '*' && newLabel !== '...' && newLabel.trim() !== '') {
SearchHistoryManager.addToHistory(newLabel);
}
// Add selected label to search history (except for special cases)
if (newLabel && newLabel !== '*' && newLabel !== '...' && newLabel.trim() !== '') {
SearchHistoryManager.addToHistory(newLabel);
}
// Reset graphDataFetchAttempted flag to ensure data fetch is triggered
useGraphStore.getState().setGraphDataFetchAttempted(false);
// Reset graphDataFetchAttempted flag to ensure data fetch is triggered
useGraphStore.getState().setGraphDataFetchAttempted(false);
// Update the label to trigger data loading
useSettingsStore.getState().setQueryLabel(newLabel);
}}
clearable={false} // Prevent clearing value on reselect
debounceTime={500}
/>
// Update the label to trigger data loading
useSettingsStore.getState().setQueryLabel(newLabel);
}}
clearable={false} // Prevent clearing value on reselect
debounceTime={500}
/>
</div>
</div>
)
}

View file

@ -45,7 +45,13 @@ export type MessageWithError = Message & {
}
// 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 { theme } = useTheme()
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`}
>
{/* Thinking process display - only for assistant messages */}
{/* Always render to prevent layout shift when switching tabs */}
{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
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={() => {
@ -161,7 +172,8 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
>
{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>
</>
) : (
@ -247,11 +259,12 @@ export const ChatMessage = ({ message }: { message: MessageWithError }) => { //
</ReactMarkdown>
</div>
)}
{(() => {
{/* Loading indicator - only show in active tab */}
{isTabActive && (() => {
// More comprehensive loading state check
const hasVisibleContent = finalDisplayContent && finalDisplayContent.trim() !== '';
const isLoadingState = !hasVisibleContent && !isThinking && !thinkingTime;
return isLoadingState && <LoaderIcon className="animate-spin duration-2000" />;
return isLoadingState && <LoaderIcon className="animate-spin duration-2000" />
})()}
</div>
)

View file

@ -103,6 +103,10 @@ const parseCOTContent = (content: string) => {
export default function RetrievalTesting() {
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[]>(() => {
try {
const history = useSettingsStore.getState().retrievalHistory || []
@ -716,7 +720,7 @@ export default function RetrievalTesting() {
<CopyIcon className="size-4" />
</Button>
)}
<ChatMessage message={message} />
<ChatMessage message={message} isTabActive={isRetrievalTabActive} />
{message.role === 'assistant' && (
<Button
onClick={() => handleCopyMessage(message)}

View file

@ -40,7 +40,7 @@ export default defineConfig({
changeOrigin: true,
rewrite: endpoint === '/api' ?
(path) => path.replace(/^\/api/, '') :
endpoint === '/docs' || endpoint === '/openapi.json' ?
endpoint === '/docs' || endpoint === '/redoc' || endpoint === '/openapi.json' ?
(path) => path : undefined
}
])