feat: Enhance multi-tenant support by allowing unauthenticated access and updating document routes to use tenant-specific RAG instances
This commit is contained in:
parent
730c406749
commit
e4962dd2a5
12 changed files with 286 additions and 73 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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<DocsStatusesResponse | null>(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 (
|
||||
|
|
|
|||
|
|
@ -153,7 +153,14 @@
|
|||
"showButton": "عرض",
|
||||
"hideButton": "إخفاء",
|
||||
"showFileNameTooltip": "عرض اسم الملف",
|
||||
"hideFileNameTooltip": "إخفاء اسم الملف"
|
||||
"hideFileNameTooltip": "إخفاء اسم الملف",
|
||||
"loading": "جارٍ تحميل المستندات...",
|
||||
"loadingHint": "قد يستغرق هذا لحظة",
|
||||
"emptyWithPipelineTitle": "المعالجة قيد التقدم",
|
||||
"emptyWithPipelineDescription": "يتم معالجة المستندات في خط الأنابيب. يمكنك مسح المستندات الجديدة أو عرض حالة خط الأنابيب.",
|
||||
"scanForDocuments": "مسح المستندات",
|
||||
"viewPipeline": "عرض خط الأنابيب",
|
||||
"emptyHint": "قم بتحميل المستندات باستخدام الزر أعلاه أو امسح المستندات في مجلد الإدخال."
|
||||
},
|
||||
"pipelineStatus": {
|
||||
"title": "حالة خط الأنابيب",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -153,7 +153,14 @@
|
|||
"showButton": "显示",
|
||||
"hideButton": "隐藏",
|
||||
"showFileNameTooltip": "显示文件名",
|
||||
"hideFileNameTooltip": "隐藏文件名"
|
||||
"hideFileNameTooltip": "隐藏文件名",
|
||||
"loading": "正在加载文档...",
|
||||
"loadingHint": "请稍候",
|
||||
"emptyWithPipelineTitle": "处理进行中",
|
||||
"emptyWithPipelineDescription": "文档正在流水线中处理。您可以扫描新文档或查看流水线状态。",
|
||||
"scanForDocuments": "扫描文档",
|
||||
"viewPipeline": "查看流水线",
|
||||
"emptyHint": "使用上方按钮上传文档,或扫描输入文件夹中的文档。"
|
||||
},
|
||||
"pipelineStatus": {
|
||||
"title": "流水线状态",
|
||||
|
|
|
|||
|
|
@ -153,7 +153,14 @@
|
|||
"showButton": "顯示",
|
||||
"hideButton": "隱藏",
|
||||
"showFileNameTooltip": "顯示檔案名稱",
|
||||
"hideFileNameTooltip": "隱藏檔案名稱"
|
||||
"hideFileNameTooltip": "隱藏檔案名稱",
|
||||
"loading": "正在載入文件...",
|
||||
"loadingHint": "請稍候",
|
||||
"emptyWithPipelineTitle": "處理進行中",
|
||||
"emptyWithPipelineDescription": "文件正在流水線中處理。您可以掃描新文件或查看流水線狀態。",
|
||||
"scanForDocuments": "掃描文件",
|
||||
"viewPipeline": "查看流水線",
|
||||
"emptyHint": "使用上方按鈕上傳文件,或掃描輸入資料夾中的文件。"
|
||||
},
|
||||
"pipelineStatus": {
|
||||
"title": "流水線狀態",
|
||||
|
|
|
|||
21
logs/2025-01-25-16-35-beastmode-translation-fix.md
Normal file
21
logs/2025-01-25-16-35-beastmode-translation-fix.md
Normal file
|
|
@ -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
|
||||
28
logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md
Normal file
28
logs/2025-01-25-16-45-beastmode-pipeline-status-fix.md
Normal file
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue