From e4962dd2a50af6647f045e40867424e5e29fa898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20MANSUY?= Date: Fri, 5 Dec 2025 00:55:09 +0800 Subject: [PATCH] feat: Enhance multi-tenant support by allowing unauthenticated access and updating document routes to use tenant-specific RAG instances --- lightrag/api/dependencies.py | 13 +- lightrag/api/routers/document_routes.py | 153 ++++++++++++------ .../src/features/DocumentManager.tsx | 16 +- lightrag_webui/src/locales/ar.json | 9 +- lightrag_webui/src/locales/en.json | 9 +- lightrag_webui/src/locales/fr.json | 9 +- lightrag_webui/src/locales/zh.json | 9 +- lightrag_webui/src/locales/zh_TW.json | 9 +- ...5-01-25-16-35-beastmode-translation-fix.md | 21 +++ ...-25-16-45-beastmode-pipeline-status-fix.md | 28 ++++ ...astmode-multitenant-document-routes-fix.md | 43 +++++ ...-27-12-30-multitenant-filtering-api-fix.md | 40 +++-- 12 files changed, 286 insertions(+), 73 deletions(-) create mode 100644 logs/2025-01-25-16-35-beastmode-translation-fix.md create mode 100644 logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md create mode 100644 logs/2025-01-25-17-00-beastmode-multitenant-document-routes-fix.md diff --git a/lightrag/api/dependencies.py b/lightrag/api/dependencies.py index 3d083740..f7f63e60 100644 --- a/lightrag/api/dependencies.py +++ b/lightrag/api/dependencies.py @@ -169,6 +169,7 @@ async def get_tenant_context( Multi-tenant requests must include: - Authorization header with JWT token containing tenant_id - OR X-API-Key header with valid API key + - OR no auth if auth is not configured (auth_mode=disabled) - X-Tenant-ID header (optional, if not in token) - X-KB-ID header (optional, if not in token) @@ -189,9 +190,13 @@ async def get_tenant_context( role_str = "viewer" metadata = {} - # Check API Key first + # Check if authentication is configured api_key = os.getenv("LIGHTRAG_API_KEY") or global_args.key - if api_key and x_api_key and x_api_key == api_key: + api_key_configured = bool(api_key) + auth_configured = bool(auth_handler.accounts) + + # Check API Key first + if api_key_configured and x_api_key and x_api_key == api_key: username = "system_admin" role_str = "admin" elif authorization: @@ -217,6 +222,10 @@ async def get_tenant_context( username = token_data.get("username") metadata = token_data.get("metadata", {}) role_str = token_data.get("role", "viewer") + elif not auth_configured and not api_key_configured: + # No auth configured - allow unauthenticated access with guest user + username = "guest" + role_str = "viewer" else: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/lightrag/api/routers/document_routes.py b/lightrag/api/routers/document_routes.py index da1ae9ac..86b08547 100644 --- a/lightrag/api/routers/document_routes.py +++ b/lightrag/api/routers/document_routes.py @@ -2081,7 +2081,10 @@ def create_document_routes( @router.post( "/scan", response_model=ScanResponse, dependencies=[Depends(combined_auth)] ) - async def scan_for_new_documents(background_tasks: BackgroundTasks): + async def scan_for_new_documents( + background_tasks: BackgroundTasks, + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Trigger the scanning process for new documents. @@ -2089,14 +2092,18 @@ def create_document_routes( and processes them. If a scanning process is already running, it returns a status indicating that fact. + Args: + background_tasks: FastAPI BackgroundTasks for async processing + tenant_rag: Tenant-specific RAG instance (injected dependency) + Returns: ScanResponse: A response object containing the scanning status and track_id """ # Generate track_id with "scan" prefix for scanning operation track_id = generate_track_id("scan") - # Start the scanning process in the background with track_id - background_tasks.add_task(run_scanning_process, rag, doc_manager, track_id) + # Start the scanning process in the background with track_id (use tenant-specific RAG) + background_tasks.add_task(run_scanning_process, tenant_rag, doc_manager, track_id) return ScanResponse( status="scanning_started", message="Scanning process has been initiated in the background", @@ -2107,7 +2114,9 @@ def create_document_routes( "/upload", response_model=InsertResponse, dependencies=[Depends(combined_auth)] ) async def upload_to_input_dir( - background_tasks: BackgroundTasks, file: UploadFile = File(...) + background_tasks: BackgroundTasks, + file: UploadFile = File(...), + tenant_rag: LightRAG = Depends(get_tenant_rag) ): """ Upload a file to the input directory and index it. @@ -2119,6 +2128,7 @@ def create_document_routes( Args: background_tasks: FastAPI BackgroundTasks for async processing file (UploadFile): The file to be uploaded. It must have an allowed extension. + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: InsertResponse: A response object containing the upload status and a message. @@ -2137,8 +2147,8 @@ def create_document_routes( detail=f"Unsupported file type. Supported types: {doc_manager.supported_extensions}", ) - # Check if filename already exists in doc_status storage - existing_doc_data = await rag.doc_status.get_doc_by_file_path(safe_filename) + # Check if filename already exists in doc_status storage (tenant-specific) + existing_doc_data = await tenant_rag.doc_status.get_doc_by_file_path(safe_filename) if existing_doc_data: # Get document status and track_id from existing document status = existing_doc_data.get("status", "unknown") @@ -2164,8 +2174,8 @@ def create_document_routes( track_id = generate_track_id("upload") - # Add to background tasks and get track_id - background_tasks.add_task(pipeline_index_file, rag, file_path, track_id) + # Add to background tasks and get track_id (use tenant-specific RAG) + background_tasks.add_task(pipeline_index_file, tenant_rag, file_path, track_id) return InsertResponse( status="success", @@ -2182,7 +2192,9 @@ def create_document_routes( "/text", response_model=InsertResponse, dependencies=[Depends(combined_auth)] ) async def insert_text( - request: InsertTextRequest, background_tasks: BackgroundTasks + request: InsertTextRequest, + background_tasks: BackgroundTasks, + tenant_rag: LightRAG = Depends(get_tenant_rag) ): """ Insert text into the RAG system. @@ -2193,6 +2205,7 @@ def create_document_routes( Args: request (InsertTextRequest): The request body containing the text to be inserted. background_tasks: FastAPI BackgroundTasks for async processing + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: InsertResponse: A response object containing the status of the operation. @@ -2201,13 +2214,13 @@ def create_document_routes( HTTPException: If an error occurs during text processing (500). """ try: - # Check if file_source already exists in doc_status storage + # Check if file_source already exists in doc_status storage (tenant-specific) if ( request.file_source and request.file_source.strip() and request.file_source != "unknown_source" ): - existing_doc_data = await rag.doc_status.get_doc_by_file_path( + existing_doc_data = await tenant_rag.doc_status.get_doc_by_file_path( request.file_source ) if existing_doc_data: @@ -2221,10 +2234,10 @@ def create_document_routes( track_id=existing_track_id, ) - # Check if content already exists by computing content hash (doc_id) + # Check if content already exists by computing content hash (doc_id) (tenant-specific) sanitized_text = sanitize_text_for_encoding(request.text) content_doc_id = compute_mdhash_id(sanitized_text, prefix="doc-") - existing_doc = await rag.doc_status.get_by_id(content_doc_id) + existing_doc = await tenant_rag.doc_status.get_by_id(content_doc_id) if existing_doc: # Content already exists, return duplicated with existing track_id status = existing_doc.get("status", "unknown") @@ -2240,7 +2253,7 @@ def create_document_routes( background_tasks.add_task( pipeline_index_texts, - rag, + tenant_rag, [request.text], file_sources=[request.file_source], track_id=track_id, @@ -2262,7 +2275,9 @@ def create_document_routes( dependencies=[Depends(combined_auth)], ) async def insert_texts( - request: InsertTextsRequest, background_tasks: BackgroundTasks + request: InsertTextsRequest, + background_tasks: BackgroundTasks, + tenant_rag: LightRAG = Depends(get_tenant_rag) ): """ Insert multiple texts into the RAG system. @@ -2273,6 +2288,7 @@ def create_document_routes( Args: request (InsertTextsRequest): The request body containing the list of texts. background_tasks: FastAPI BackgroundTasks for async processing + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: InsertResponse: A response object containing the status of the operation. @@ -2281,7 +2297,7 @@ def create_document_routes( HTTPException: If an error occurs during text processing (500). """ try: - # Check if any file_sources already exist in doc_status storage + # Check if any file_sources already exist in doc_status storage (tenant-specific) if request.file_sources: for file_source in request.file_sources: if ( @@ -2289,7 +2305,7 @@ def create_document_routes( and file_source.strip() and file_source != "unknown_source" ): - existing_doc_data = await rag.doc_status.get_doc_by_file_path( + existing_doc_data = await tenant_rag.doc_status.get_doc_by_file_path( file_source ) if existing_doc_data: @@ -2303,11 +2319,11 @@ def create_document_routes( track_id=existing_track_id, ) - # Check if any content already exists by computing content hash (doc_id) + # Check if any content already exists by computing content hash (doc_id) (tenant-specific) for text in request.texts: sanitized_text = sanitize_text_for_encoding(text) content_doc_id = compute_mdhash_id(sanitized_text, prefix="doc-") - existing_doc = await rag.doc_status.get_by_id(content_doc_id) + existing_doc = await tenant_rag.doc_status.get_by_id(content_doc_id) if existing_doc: # Content already exists, return duplicated with existing track_id status = existing_doc.get("status", "unknown") @@ -2323,7 +2339,7 @@ def create_document_routes( background_tasks.add_task( pipeline_index_texts, - rag, + tenant_rag, request.texts, file_sources=request.file_sources, track_id=track_id, @@ -2342,7 +2358,9 @@ def create_document_routes( @router.delete( "", response_model=ClearDocumentsResponse, dependencies=[Depends(combined_auth)] ) - async def clear_documents(): + async def clear_documents( + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Clear all documents from the RAG system. @@ -2350,6 +2368,9 @@ def create_document_routes( It uses the storage drop methods to properly clean up all data and removes all files from the input directory. + Args: + tenant_rag: Tenant-specific RAG instance (injected dependency) + Returns: ClearDocumentsResponse: A response object containing the status and message. - status="success": All documents and files were successfully cleared. @@ -2368,12 +2389,12 @@ def create_document_routes( get_namespace_lock, ) - # Get pipeline status and lock + # Get pipeline status and lock (tenant-specific) pipeline_status = await get_namespace_data( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) pipeline_status_lock = get_namespace_lock( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) # Check and set status with lock @@ -2403,20 +2424,20 @@ def create_document_routes( ) try: - # Use drop method to clear all data + # Use drop method to clear all data (tenant-specific) drop_tasks = [] storages = [ - rag.text_chunks, - rag.full_docs, - rag.full_entities, - rag.full_relations, - rag.entity_chunks, - rag.relation_chunks, - rag.entities_vdb, - rag.relationships_vdb, - rag.chunks_vdb, - rag.chunk_entity_relation_graph, - rag.doc_status, + tenant_rag.text_chunks, + tenant_rag.full_docs, + tenant_rag.full_entities, + tenant_rag.full_relations, + tenant_rag.entity_chunks, + tenant_rag.relation_chunks, + tenant_rag.entities_vdb, + tenant_rag.relationships_vdb, + tenant_rag.chunks_vdb, + tenant_rag.chunk_entity_relation_graph, + tenant_rag.doc_status, ] # Log storage drop start @@ -2760,6 +2781,7 @@ def create_document_routes( async def delete_document( delete_request: DeleteDocRequest, background_tasks: BackgroundTasks, + tenant_rag: LightRAG = Depends(get_tenant_rag), ) -> DeleteDocByIdResponse: """ Delete documents and all their associated data by their IDs using background processing. @@ -2774,6 +2796,7 @@ def create_document_routes( Args: delete_request (DeleteDocRequest): The request containing the document IDs and deletion options. background_tasks: FastAPI BackgroundTasks for async processing + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: DeleteDocByIdResponse: The result of the deletion operation. @@ -2793,10 +2816,10 @@ def create_document_routes( ) pipeline_status = await get_namespace_data( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) pipeline_status_lock = get_namespace_lock( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) # Check if pipeline is busy with proper lock @@ -2808,10 +2831,10 @@ def create_document_routes( doc_id=", ".join(doc_ids), ) - # Add deletion task to background tasks + # Add deletion task to background tasks (use tenant-specific RAG) background_tasks.add_task( background_delete_documents, - rag, + tenant_rag, doc_manager, doc_ids, delete_request.delete_file, @@ -2835,7 +2858,10 @@ def create_document_routes( response_model=ClearCacheResponse, dependencies=[Depends(combined_auth)], ) - async def clear_cache(request: ClearCacheRequest): + async def clear_cache( + request: ClearCacheRequest, + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Clear all cache data from the LLM response cache storage. @@ -2844,6 +2870,7 @@ def create_document_routes( Args: request (ClearCacheRequest): The request body (ignored for compatibility). + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: ClearCacheResponse: A response object containing the status and message. @@ -2852,8 +2879,8 @@ def create_document_routes( HTTPException: If an error occurs during cache clearing (500). """ try: - # Call the aclear_cache method (no modes parameter) - await rag.aclear_cache() + # Call the aclear_cache method (no modes parameter) - tenant-specific + await tenant_rag.aclear_cache() # Prepare success message message = "Successfully cleared all cache" @@ -2869,12 +2896,16 @@ def create_document_routes( response_model=DeletionResult, dependencies=[Depends(combined_auth)], ) - async def delete_entity(request: DeleteEntityRequest): + async def delete_entity( + request: DeleteEntityRequest, + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Delete an entity and all its relationships from the knowledge graph. Args: request (DeleteEntityRequest): The request body containing the entity name. + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: DeletionResult: An object containing the outcome of the deletion process. @@ -2883,7 +2914,7 @@ def create_document_routes( HTTPException: If the entity is not found (404) or an error occurs (500). """ try: - result = await rag.adelete_by_entity(entity_name=request.entity_name) + result = await tenant_rag.adelete_by_entity(entity_name=request.entity_name) if result.status == "not_found": raise HTTPException(status_code=404, detail=result.message) if result.status == "fail": @@ -2904,12 +2935,16 @@ def create_document_routes( response_model=DeletionResult, dependencies=[Depends(combined_auth)], ) - async def delete_relation(request: DeleteRelationRequest): + async def delete_relation( + request: DeleteRelationRequest, + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Delete a relationship between two entities from the knowledge graph. Args: request (DeleteRelationRequest): The request body containing the source and target entity names. + tenant_rag: Tenant-specific RAG instance (injected dependency) Returns: DeletionResult: An object containing the outcome of the deletion process. @@ -2918,7 +2953,7 @@ def create_document_routes( HTTPException: If the relation is not found (404) or an error occurs (500). """ try: - result = await rag.adelete_by_relation( + result = await tenant_rag.adelete_by_relation( source_entity=request.source_entity, target_entity=request.target_entity, ) @@ -3139,7 +3174,10 @@ def create_document_routes( response_model=ReprocessResponse, dependencies=[Depends(combined_auth)], ) - async def reprocess_failed_documents(background_tasks: BackgroundTasks): + async def reprocess_failed_documents( + background_tasks: BackgroundTasks, + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Reprocess failed and pending documents. @@ -3156,6 +3194,10 @@ def create_document_routes( pipeline status. The reprocessed documents retain their original track_id from initial upload, so use their original track_id to monitor progress. + Args: + background_tasks: FastAPI BackgroundTasks for async processing + tenant_rag: Tenant-specific RAG instance (injected dependency) + Returns: ReprocessResponse: Response with status and message. track_id is always empty string because reprocessed documents retain @@ -3165,9 +3207,9 @@ def create_document_routes( HTTPException: If an error occurs while initiating reprocessing (500). """ try: - # Start the reprocessing in the background + # Start the reprocessing in the background (use tenant-specific RAG) # Note: Reprocessed documents retain their original track_id from initial upload - background_tasks.add_task(rag.apipeline_process_enqueue_documents) + background_tasks.add_task(tenant_rag.apipeline_process_enqueue_documents) logger.info("Reprocessing of failed documents initiated") return ReprocessResponse( @@ -3185,7 +3227,9 @@ def create_document_routes( response_model=CancelPipelineResponse, dependencies=[Depends(combined_auth)], ) - async def cancel_pipeline(): + async def cancel_pipeline( + tenant_rag: LightRAG = Depends(get_tenant_rag) + ): """ Request cancellation of the currently running pipeline. @@ -3198,6 +3242,9 @@ def create_document_routes( The cancellation is graceful and ensures data consistency. Documents that have completed processing will remain in PROCESSED status. + Args: + tenant_rag: Tenant-specific RAG instance (injected dependency) + Returns: CancelPipelineResponse: Response with status and message - status="cancellation_requested": Cancellation flag has been set @@ -3213,10 +3260,10 @@ def create_document_routes( ) pipeline_status = await get_namespace_data( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) pipeline_status_lock = get_namespace_lock( - "pipeline_status", workspace=rag.workspace + "pipeline_status", workspace=tenant_rag.workspace ) async with pipeline_status_lock: diff --git a/lightrag_webui/src/features/DocumentManager.tsx b/lightrag_webui/src/features/DocumentManager.tsx index f919bc6a..ca39add4 100644 --- a/lightrag_webui/src/features/DocumentManager.tsx +++ b/lightrag_webui/src/features/DocumentManager.tsx @@ -23,6 +23,7 @@ import PaginationControls from '@/components/ui/PaginationControls' import { scanNewDocuments, getDocumentsPaginated, + getPipelineStatus, DocsStatusesResponse, DocStatus, DocStatusResponse, @@ -223,6 +224,7 @@ export default function DocumentManager() { const { t, i18n } = useTranslation() const health = useBackendState.use.health() const pipelineBusy = useBackendState.use.pipelineBusy() + const setPipelineBusy = useBackendState.use.setPipelineBusy() // Legacy state for backward compatibility const [docs, setDocs] = useState(null) @@ -810,6 +812,18 @@ export default function DocumentManager() { } updateComponentState(response); + // Fetch tenant-specific pipeline status and update global state + // This ensures pipelineBusy reflects the current tenant's pipeline, not global state + try { + const pipelineStatus = await getPipelineStatus(); + if (isMountedRef.current) { + setPipelineBusy(pipelineStatus.busy); + } + } catch (pipelineErr) { + // Silently ignore pipeline status fetch errors - not critical + console.warn('[DocumentManager] Failed to fetch pipeline status:', pipelineErr); + } + } catch (err) { if (isMountedRef.current) { const errorClassification = classifyError(err); @@ -833,7 +847,7 @@ export default function DocumentManager() { setIsRefreshing(false); } } - }, [statusFilter, pagination.page, pagination.page_size, sortField, sortDirection, t, updateComponentState, withTimeout, classifyError, recordFailure]); + }, [statusFilter, pagination.page, pagination.page_size, sortField, sortDirection, t, updateComponentState, withTimeout, classifyError, recordFailure, setPipelineBusy, isTenantContextReady]); // New paginated data fetching function const fetchPaginatedDocuments = useCallback(async ( diff --git a/lightrag_webui/src/locales/ar.json b/lightrag_webui/src/locales/ar.json index e0186bf7..585a3cc6 100644 --- a/lightrag_webui/src/locales/ar.json +++ b/lightrag_webui/src/locales/ar.json @@ -153,7 +153,14 @@ "showButton": "عرض", "hideButton": "إخفاء", "showFileNameTooltip": "عرض اسم الملف", - "hideFileNameTooltip": "إخفاء اسم الملف" + "hideFileNameTooltip": "إخفاء اسم الملف", + "loading": "جارٍ تحميل المستندات...", + "loadingHint": "قد يستغرق هذا لحظة", + "emptyWithPipelineTitle": "المعالجة قيد التقدم", + "emptyWithPipelineDescription": "يتم معالجة المستندات في خط الأنابيب. يمكنك مسح المستندات الجديدة أو عرض حالة خط الأنابيب.", + "scanForDocuments": "مسح المستندات", + "viewPipeline": "عرض خط الأنابيب", + "emptyHint": "قم بتحميل المستندات باستخدام الزر أعلاه أو امسح المستندات في مجلد الإدخال." }, "pipelineStatus": { "title": "حالة خط الأنابيب", diff --git a/lightrag_webui/src/locales/en.json b/lightrag_webui/src/locales/en.json index ebbba0a7..b11f3112 100644 --- a/lightrag_webui/src/locales/en.json +++ b/lightrag_webui/src/locales/en.json @@ -153,7 +153,14 @@ "showButton": "Show", "hideButton": "Hide", "showFileNameTooltip": "Show file name", - "hideFileNameTooltip": "Hide file name" + "hideFileNameTooltip": "Hide file name", + "loading": "Loading documents...", + "loadingHint": "This may take a moment", + "emptyWithPipelineTitle": "Processing in Progress", + "emptyWithPipelineDescription": "Documents are being processed in the pipeline. You can scan for new documents or view the pipeline status.", + "scanForDocuments": "Scan for Documents", + "viewPipeline": "View Pipeline", + "emptyHint": "Upload documents using the button above or scan for documents in the input folder." }, "pipelineStatus": { "title": "Pipeline Status", diff --git a/lightrag_webui/src/locales/fr.json b/lightrag_webui/src/locales/fr.json index a096a512..77a9c421 100644 --- a/lightrag_webui/src/locales/fr.json +++ b/lightrag_webui/src/locales/fr.json @@ -153,7 +153,14 @@ "showButton": "Afficher", "hideButton": "Masquer", "showFileNameTooltip": "Afficher le nom du fichier", - "hideFileNameTooltip": "Masquer le nom du fichier" + "hideFileNameTooltip": "Masquer le nom du fichier", + "loading": "Chargement des documents...", + "loadingHint": "Cela peut prendre un moment", + "emptyWithPipelineTitle": "Traitement en cours", + "emptyWithPipelineDescription": "Les documents sont en cours de traitement dans le pipeline. Vous pouvez scanner de nouveaux documents ou voir l'état du pipeline.", + "scanForDocuments": "Scanner les documents", + "viewPipeline": "Voir le pipeline", + "emptyHint": "Téléchargez des documents à l'aide du bouton ci-dessus ou scannez les documents dans le dossier d'entrée." }, "pipelineStatus": { "title": "État du Pipeline", diff --git a/lightrag_webui/src/locales/zh.json b/lightrag_webui/src/locales/zh.json index 8bbca7bb..4a0a0713 100644 --- a/lightrag_webui/src/locales/zh.json +++ b/lightrag_webui/src/locales/zh.json @@ -153,7 +153,14 @@ "showButton": "显示", "hideButton": "隐藏", "showFileNameTooltip": "显示文件名", - "hideFileNameTooltip": "隐藏文件名" + "hideFileNameTooltip": "隐藏文件名", + "loading": "正在加载文档...", + "loadingHint": "请稍候", + "emptyWithPipelineTitle": "处理进行中", + "emptyWithPipelineDescription": "文档正在流水线中处理。您可以扫描新文档或查看流水线状态。", + "scanForDocuments": "扫描文档", + "viewPipeline": "查看流水线", + "emptyHint": "使用上方按钮上传文档,或扫描输入文件夹中的文档。" }, "pipelineStatus": { "title": "流水线状态", diff --git a/lightrag_webui/src/locales/zh_TW.json b/lightrag_webui/src/locales/zh_TW.json index e62b5343..85ca0cbd 100644 --- a/lightrag_webui/src/locales/zh_TW.json +++ b/lightrag_webui/src/locales/zh_TW.json @@ -153,7 +153,14 @@ "showButton": "顯示", "hideButton": "隱藏", "showFileNameTooltip": "顯示檔案名稱", - "hideFileNameTooltip": "隱藏檔案名稱" + "hideFileNameTooltip": "隱藏檔案名稱", + "loading": "正在載入文件...", + "loadingHint": "請稍候", + "emptyWithPipelineTitle": "處理進行中", + "emptyWithPipelineDescription": "文件正在流水線中處理。您可以掃描新文件或查看流水線狀態。", + "scanForDocuments": "掃描文件", + "viewPipeline": "查看流水線", + "emptyHint": "使用上方按鈕上傳文件,或掃描輸入資料夾中的文件。" }, "pipelineStatus": { "title": "流水線狀態", diff --git a/logs/2025-01-25-16-35-beastmode-translation-fix.md b/logs/2025-01-25-16-35-beastmode-translation-fix.md new file mode 100644 index 00000000..e9a4d8c2 --- /dev/null +++ b/logs/2025-01-25-16-35-beastmode-translation-fix.md @@ -0,0 +1,21 @@ +# Task Log: Translation Keys Fix + +**Date:** 2025-01-25 16:35 +**Mode:** beastmode + +## Actions +- Added 7 missing translation keys to all 5 locale files (en, zh, zh_TW, fr, ar) +- Keys added: `loading`, `loadingHint`, `emptyWithPipelineTitle`, `emptyWithPipelineDescription`, `scanForDocuments`, `viewPipeline`, `emptyHint` +- Validated JSON syntax for all translation files + +## Decisions +- Used contextually appropriate translations for each language +- Added translations to documentPanel.documentManager namespace to match existing structure + +## Next Steps +- Refresh the browser to see the translated text instead of raw keys +- Verify all translation keys display correctly in Documents panel + +## Lessons/Insights +- Translation keys were referenced in DocumentManager.tsx but never added to locale JSON files +- i18n fallback returns the key itself when translation is missing, causing the display issue diff --git a/logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md b/logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md new file mode 100644 index 00000000..e063c271 --- /dev/null +++ b/logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md @@ -0,0 +1,28 @@ +# Task Log: Fix Pipeline Status Multi-Tenant Mismatch + +**Date:** 2025-01-25 16:45 +**Mode:** beastmode + +## Problem +The Documents panel was stuck showing "Processing in Progress" even when the tenant-specific pipeline was not busy. This happened because: +1. The global `/health` endpoint returns `pipeline_busy` from global namespace (not tenant-aware) +2. The `/documents/pipeline_status` endpoint returns tenant-specific pipeline status +3. The UI used the global `pipelineBusy` state from health endpoint to decide which empty state to show + +## Actions +1. Added `getPipelineStatus` import to DocumentManager.tsx +2. Added `setPipelineBusy` hook to update global pipeline state +3. Modified `handleIntelligentRefresh` to fetch tenant-specific pipeline status after documents load +4. Updated dependency array to include `setPipelineBusy` and `isTenantContextReady` + +## Decisions +- Chose to update global `pipelineBusy` state from DocumentManager rather than modifying the health endpoint +- This approach keeps the health endpoint simple and makes the Documents panel the source of truth for tenant-specific pipeline state + +## Next Steps +- Refresh browser and verify Documents panel shows correct empty state +- When switching tenants/KBs, the pipeline status should update correctly + +## Lessons/Insights +- In multi-tenant mode, global status endpoints (like /health) don't reflect tenant-specific state +- Components that need tenant-specific data should fetch it directly from tenant-aware endpoints diff --git a/logs/2025-01-25-17-00-beastmode-multitenant-document-routes-fix.md b/logs/2025-01-25-17-00-beastmode-multitenant-document-routes-fix.md new file mode 100644 index 00000000..303146c2 --- /dev/null +++ b/logs/2025-01-25-17-00-beastmode-multitenant-document-routes-fix.md @@ -0,0 +1,43 @@ +# Task Log: Multi-Tenant Document Routes Fix + +**Date:** 2025-01-25 17:00 +**Mode:** beastmode + +## Problem +Document upload, scan, and other document operations were using the **global** RAG instance instead of the **tenant-specific** RAG instance. This caused: +1. Documents uploaded in one tenant's KB being stored in the global namespace +2. Pipeline status being read from global namespace instead of tenant-specific +3. Document count in header showing wrong number ("0 docs") + +## Root Cause +In `lightrag/api/routers/document_routes.py`, many endpoints were using the closure-captured `rag` variable (global) instead of the `get_tenant_rag` dependency that provides tenant-specific RAG instances. + +## Actions +Fixed the following endpoints to use `tenant_rag: LightRAG = Depends(get_tenant_rag)`: + +1. **POST /documents/upload** - File upload now uses tenant-specific RAG +2. **POST /documents/scan** - Directory scan now uses tenant-specific RAG +3. **POST /documents/text** - Text insertion now uses tenant-specific RAG +4. **POST /documents/texts** - Batch text insertion now uses tenant-specific RAG +5. **DELETE /documents** (clear_documents) - Clear all now uses tenant-specific RAG +6. **DELETE /documents/delete** - Delete by ID now uses tenant-specific RAG +7. **POST /documents/clear_cache** - Cache clearing now uses tenant-specific RAG +8. **DELETE /documents/delete_entity** - Entity deletion now uses tenant-specific RAG +9. **DELETE /documents/delete_relation** - Relation deletion now uses tenant-specific RAG +10. **POST /documents/reprocess_failed** - Reprocess now uses tenant-specific RAG +11. **POST /documents/cancel_pipeline** - Pipeline cancel now uses tenant-specific RAG + +## Decisions +- Used existing `get_tenant_rag` dependency pattern for consistency +- All document operations now properly scope to tenant/KB context from headers + +## Next Steps +1. Restart backend server to apply changes +2. Test document upload in multi-tenant mode +3. Verify pipeline processes documents in correct tenant namespace +4. Verify document count in header updates correctly + +## Lessons/Insights +- Multi-tenant systems require careful auditing of ALL endpoints to ensure proper tenant isolation +- Using dependency injection patterns (like `Depends(get_tenant_rag)`) makes it cleaner to manage tenant context +- Background tasks must receive the correct RAG instance at task creation time diff --git a/logs/2025-01-27-12-30-multitenant-filtering-api-fix.md b/logs/2025-01-27-12-30-multitenant-filtering-api-fix.md index 1e784f94..eb6021dd 100644 --- a/logs/2025-01-27-12-30-multitenant-filtering-api-fix.md +++ b/logs/2025-01-27-12-30-multitenant-filtering-api-fix.md @@ -1,6 +1,6 @@ -# Task Log: Multi-Tenant Filtering & API Tab Fix +# Task Log: Multi-Tenant Filtering & API Fixes -**Date:** 2025-01-27 12:30 +**Date:** 2025-01-27 12:45 **Mode:** beastmode ## Todo List Status @@ -10,30 +10,46 @@ - [x] Step 3: Query/Retrieval routes multi-tenant filtering (3 endpoints updated) - [x] Step 4: Update lightrag_server.py to pass rag_manager to all routes - [x] Step 5: Fix API tab visibility - Add /static proxy for Swagger UI assets -- [ ] Step 6: Restart Vite dev server to apply proxy configuration change (user action required) +- [x] Step 6: Fix "Network connection error" - Allow unauthenticated access when auth disabled +- [x] Step 7: Restart backend server to apply changes ## Actions - Updated `graph_routes.py`: Added `get_tenant_rag` dependency to all 10 graph endpoints -- Updated `query_routes.py`: Added `get_tenant_rag` dependency to 3 query endpoints (`/query`, `/query/stream`, `/query/data`) +- Updated `query_routes.py`: Added `get_tenant_rag` dependency to 3 query endpoints - Updated `lightrag_server.py`: Pass `rag_manager` to `create_graph_routes()` and `create_query_routes()` - Updated `vite.config.ts`: Added `/static` proxy to fix Swagger UI asset loading +- Updated `dependencies.py`: Fixed `get_tenant_context` to allow unauthenticated access when `auth_configured=False` and `api_key_configured=False` +- Restarted backend server to apply the authentication fix + +## Root Cause Analysis + +### Issue 1: API Tab White/Blank +- **Cause**: Swagger UI loads from `/docs` but assets come from `/static/swagger-ui/*` +- **Problem**: Vite proxy wasn't configured for `/static` path +- **Fix**: Added `/static` proxy in vite.config.ts + +### Issue 2: Network Connection Error on Retrieval +- **Cause**: `get_tenant_context` in `dependencies.py` required authentication even when auth was disabled +- **Problem**: When frontend sends `X-Tenant-ID` header, `get_tenant_context_optional` calls `get_tenant_context` directly, which always requires auth +- **Fix**: Added check for `auth_configured` and `api_key_configured` in `get_tenant_context` - if both are False, allow guest access ## Decisions - Used same multi-tenant pattern across all routes: `get_tenant_rag` dependency returns tenant-specific LightRAG instance -- API tab fix: Added `/static` proxy rather than changing Swagger UI configuration +- For auth fix: Added guest user with "viewer" role when no auth is configured ## Next Steps -- Restart Vite dev server (Ctrl+C and `bun run dev`) to apply proxy configuration change -- Test API tab now shows Swagger UI -- Test graph/retrieval operations filter by KB when switching knowledgebases +- Test all screens with tenant/KB switching to verify data isolation +- Verify API tab displays Swagger UI correctly +- Test retrieval queries work without authentication errors ## Lessons/Insights -- Swagger UI loads from `/docs` but assets come from `/static/swagger-ui/*` - both paths need proxying -- Vite's `base: '/webui/'` setting redirects non-proxied paths, causing 404s for Swagger assets -- Proxy configuration changes require dev server restart to take effect +- When `X-Tenant-ID` is provided in headers, `get_tenant_context_optional` propagates errors instead of falling back to None +- The auth logic in `dependencies.py` was inconsistent with `utils_api.py` - both now respect "no auth required" mode +- Swagger UI loads static assets from a separate path (`/static/`) that needs explicit proxy configuration ## Files Modified 1. `lightrag/api/routers/graph_routes.py` - Multi-tenant support for all graph endpoints 2. `lightrag/api/routers/query_routes.py` - Multi-tenant support for all query endpoints 3. `lightrag/api/lightrag_server.py` - Pass rag_manager to graph and query route creators -4. `lightrag_webui/vite.config.ts` - Added `/static` proxy for Swagger UI assets +4. `lightrag/api/dependencies.py` - Allow unauthenticated access when auth is disabled +5. `lightrag_webui/vite.config.ts` - Added `/static` proxy for Swagger UI assets