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
|
||||
.venv/
|
||||
env/
|
||||
venv/
|
||||
*.env*
|
||||
|
||||
# Enviroment Variable Files
|
||||
.env
|
||||
|
||||
# Build / Distribution
|
||||
dist/
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__api_version__ = "0240"
|
||||
__api_version__ = "0241"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"<\|\|(?!>)",
|
||||
|
|
|
|||
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
|
||||
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_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' : ''}`} />
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue