Compare commits
26 commits
main
...
RAGAnythin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bc5f1578c | ||
|
|
680b7c5b89 | ||
|
|
b9f7e1426c | ||
|
|
fb3dd9dbb3 | ||
|
|
d0709d5416 | ||
|
|
12028a32c4 | ||
|
|
173baf96b9 | ||
|
|
0dc11e0794 | ||
|
|
8620ce0b01 | ||
|
|
8bd8888506 | ||
|
|
8d4ef251c7 | ||
|
|
27845023e6 | ||
|
|
a33484bdb7 | ||
|
|
e07d4bb70b | ||
|
|
482a09d397 | ||
|
|
8d800239d6 | ||
|
|
e3ea87da24 | ||
|
|
2a453fbe37 | ||
|
|
7c8db78057 | ||
|
|
82a0f8cc1f | ||
|
|
e27031587d | ||
|
|
bd533783e1 | ||
|
|
cb003593df | ||
|
|
745aa085db | ||
|
|
36c81039b1 | ||
|
|
d8b2264d8b |
71 changed files with 5119 additions and 70 deletions
|
|
@ -25,6 +25,7 @@ from lightrag.api.utils_api import (
|
|||
display_splash_screen,
|
||||
check_env_file,
|
||||
)
|
||||
from lightrag.llm.openai import openai_complete_if_cache, openai_embed
|
||||
from .config import (
|
||||
global_args,
|
||||
update_uvicorn_mode_config,
|
||||
|
|
@ -61,6 +62,8 @@ from lightrag.kg.shared_storage import (
|
|||
)
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from lightrag.api.auth import auth_handler
|
||||
from lightrag.ragmanager import RAGManager
|
||||
from raganything import RAGAnything, RAGAnythingConfig
|
||||
|
||||
# use the .env that is inside the current folder
|
||||
# allows to use different .env file for each lightrag instance
|
||||
|
|
@ -619,10 +622,147 @@ def create_app(args):
|
|||
logger.error(f"Failed to initialize LightRAG: {e}")
|
||||
raise
|
||||
|
||||
# Initialize RAGAnything with comprehensive error handling
|
||||
rag_anything = None
|
||||
raganything_enabled = False
|
||||
raganything_error_message = None
|
||||
|
||||
try:
|
||||
api_key = get_env_value("LLM_BINDING_API_KEY", "", str)
|
||||
base_url = get_env_value("LLM_BINDING_HOST", "", str)
|
||||
|
||||
# Validate required configuration
|
||||
if not api_key:
|
||||
raise ValueError(
|
||||
"LLM_BINDING_API_KEY is required for RAGAnything functionality"
|
||||
)
|
||||
if not base_url:
|
||||
raise ValueError(
|
||||
"LLM_BINDING_HOST is required for RAGAnything functionality"
|
||||
)
|
||||
|
||||
config = RAGAnythingConfig(
|
||||
working_dir=args.working_dir or "./rag_storage",
|
||||
parser="mineru", # Parser selection: mineru or docling
|
||||
parse_method="auto", # Parse method: auto, ocr, or txt
|
||||
enable_image_processing=True,
|
||||
enable_table_processing=True,
|
||||
enable_equation_processing=True,
|
||||
)
|
||||
|
||||
# Define LLM model function
|
||||
def llm_model_func(prompt, system_prompt=None, history_messages=[], **kwargs):
|
||||
return openai_complete_if_cache(
|
||||
"gpt-4o-mini",
|
||||
prompt,
|
||||
system_prompt=system_prompt,
|
||||
history_messages=history_messages,
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
# Define vision model function for image processing
|
||||
def vision_model_func(
|
||||
prompt, system_prompt=None, history_messages=[], image_data=None, **kwargs
|
||||
):
|
||||
if image_data:
|
||||
return openai_complete_if_cache(
|
||||
"gpt-4o",
|
||||
"",
|
||||
system_prompt=None,
|
||||
history_messages=[],
|
||||
messages=[
|
||||
{"role": "system", "content": system_prompt}
|
||||
if system_prompt
|
||||
else None,
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": prompt},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{image_data}"
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
if image_data
|
||||
else {"role": "user", "content": prompt},
|
||||
],
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
**kwargs,
|
||||
)
|
||||
else:
|
||||
return llm_model_func(prompt, system_prompt, history_messages, **kwargs)
|
||||
|
||||
# Define embedding function
|
||||
raganything_embedding_func = EmbeddingFunc(
|
||||
embedding_dim=3072,
|
||||
max_token_size=8192,
|
||||
func=lambda texts: openai_embed(
|
||||
texts,
|
||||
model="text-embedding-3-large",
|
||||
api_key=api_key,
|
||||
base_url=base_url,
|
||||
),
|
||||
)
|
||||
|
||||
# Initialize RAGAnything with new dataclass structure
|
||||
logger.info("Initializing RAGAnything functionality...")
|
||||
rag_anything = RAGAnything(
|
||||
lightrag=rag,
|
||||
config=config,
|
||||
llm_model_func=llm_model_func,
|
||||
vision_model_func=vision_model_func,
|
||||
embedding_func=raganything_embedding_func,
|
||||
)
|
||||
|
||||
logger.info("Check the download status of the RAGAnything parser...")
|
||||
rag_anything.verify_parser_installation_once()
|
||||
|
||||
RAGManager.set_rag(rag_anything)
|
||||
raganything_enabled = True
|
||||
logger.info(
|
||||
"The RAGAnything feature has been successfully enabled, supporting multimodal document processing functionality"
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
raganything_error_message = (
|
||||
f"RAGAnything dependency package not installed: {str(e)}"
|
||||
)
|
||||
logger.warning(f"{raganything_error_message}")
|
||||
logger.info(
|
||||
"Please run 'pip install raganything' to install dependency packages to enable multimodal document processing functionality"
|
||||
)
|
||||
except ValueError as e:
|
||||
raganything_error_message = f"RAGAnything configuration error: {str(e)}"
|
||||
logger.warning(f"{raganything_error_message}")
|
||||
logger.info(
|
||||
"Please check if the environment variables LLM-BINDING_API_KEY and LLM-BINDING_HOST are set correctly"
|
||||
)
|
||||
except Exception as e:
|
||||
raganything_error_message = f"RAGAnything initialization failed: {str(e)}"
|
||||
logger.error(f" {raganything_error_message}")
|
||||
logger.info(
|
||||
"The system will run in basic mode and only support standard document processing functions"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize LightRAG: {e}")
|
||||
raise
|
||||
|
||||
if not raganything_enabled:
|
||||
logger.info(
|
||||
"The system has been downgraded to basic mode, but LightRAG core functions are still available"
|
||||
)
|
||||
|
||||
# Add routes
|
||||
app.include_router(
|
||||
create_document_routes(
|
||||
rag,
|
||||
rag_anything,
|
||||
doc_manager,
|
||||
api_key,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ This module contains all document-related routes for the LightRAG API.
|
|||
"""
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from lightrag.utils import logger, get_pinyin_sort_key
|
||||
import aiofiles
|
||||
import shutil
|
||||
|
|
@ -18,6 +20,7 @@ from fastapi import (
|
|||
File,
|
||||
HTTPException,
|
||||
UploadFile,
|
||||
Form,
|
||||
)
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
|
@ -26,6 +29,7 @@ from lightrag.base import DeletionResult, DocProcessingStatus, DocStatus
|
|||
from lightrag.utils import generate_track_id
|
||||
from lightrag.api.utils_api import get_combined_auth_dependency
|
||||
from ..config import global_args
|
||||
from raganything import RAGAnything
|
||||
|
||||
|
||||
# Function to format datetime to ISO format string with timezone information
|
||||
|
|
@ -107,6 +111,81 @@ def sanitize_filename(filename: str, input_dir: Path) -> str:
|
|||
return clean_name
|
||||
|
||||
|
||||
class SchemeConfig(BaseModel):
|
||||
"""Configuration model for processing schemes.
|
||||
|
||||
Defines the processing framework and optional extractor to use for document processing.
|
||||
|
||||
Attributes:
|
||||
framework (Literal['lightrag', 'raganything']): Processing framework to use.
|
||||
- "lightrag": Standard LightRAG processing for text-based documents
|
||||
- "raganything": Advanced multimodal processing with image/table/equation support
|
||||
extractor (Literal['mineru', 'docling', '']): Document extraction tool to use.
|
||||
- "mineru": MinerU parser for comprehensive document parsing
|
||||
- "docling": Docling parser for office document processing
|
||||
- "": Default/automatic extractor selection
|
||||
modelSource (Literal["huggingface", "modelscope", "local", ""]): The model source used by Mineru.
|
||||
- "huggingface": Using pre-trained models from the Hugging Face model library
|
||||
- "modelscope": using model resources on ModelScope platform
|
||||
- "local": Use custom models deployed locally
|
||||
- "":Maintain the default model source configuration of the system (usually huggingface)
|
||||
"""
|
||||
|
||||
framework: Literal["lightrag", "raganything"]
|
||||
extractor: Literal["mineru", "docling", ""] = "" # 默认值
|
||||
modelSource: Literal["huggingface", "modelscope", "local", ""] = ""
|
||||
|
||||
|
||||
class Scheme(BaseModel):
|
||||
"""Base model for processing schemes.
|
||||
|
||||
Attributes:
|
||||
name (str): Human-readable name for the processing scheme
|
||||
config (SchemeConfig): Configuration settings for the scheme
|
||||
"""
|
||||
|
||||
name: str
|
||||
config: SchemeConfig
|
||||
|
||||
|
||||
class Scheme_include_id(Scheme):
|
||||
"""Scheme model with unique identifier included.
|
||||
|
||||
Extends the base Scheme model to include a unique ID field for
|
||||
identification and management operations.
|
||||
|
||||
Attributes:
|
||||
id (int): Unique identifier for the scheme
|
||||
name (str): Inherited from Scheme
|
||||
config (SchemeConfig): Inherited from Scheme
|
||||
"""
|
||||
|
||||
id: int
|
||||
|
||||
|
||||
class SchemesResponse(BaseModel):
|
||||
"""Response model for scheme management operations.
|
||||
|
||||
Used for all scheme-related endpoints to provide consistent response format
|
||||
for scheme retrieval, creation, update, and deletion operations.
|
||||
|
||||
Attributes:
|
||||
status (str): Operation status ("success", "error")
|
||||
message (Optional[str]): Additional message with operation details
|
||||
data (Optional[List[Dict[str, Any]]]): List of scheme objects when retrieving schemes
|
||||
"""
|
||||
|
||||
status: str = Field(..., description="Operation status")
|
||||
message: Optional[str] = Field(None, description="Additional message")
|
||||
data: Optional[List[Dict[str, Any]]] = Field(None, description="List of schemes")
|
||||
|
||||
|
||||
class ScanRequest(BaseModel):
|
||||
"""Request model for document scanning operations."""
|
||||
|
||||
schemeConfig: SchemeConfig = Field(..., description="Scanning scheme configuration")
|
||||
|
||||
|
||||
class ScanResponse(BaseModel):
|
||||
"""Response model for document scanning operation
|
||||
|
||||
|
|
@ -372,12 +451,20 @@ class DocStatusResponse(BaseModel):
|
|||
default=None, description="Additional metadata about the document"
|
||||
)
|
||||
file_path: str = Field(description="Path to the document file")
|
||||
scheme_name: str = Field(
|
||||
default=None, description="Name of the processing scheme used for this document"
|
||||
)
|
||||
multimodal_content: Optional[list[dict[str, Any]]] = Field(
|
||||
default=None, description="Multimodal content of the document"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"id": "doc_123456",
|
||||
"content_summary": "Research paper on machine learning",
|
||||
"scheme_name": "lightrag",
|
||||
"multimodal_content": [],
|
||||
"content_length": 15240,
|
||||
"status": "PROCESSED",
|
||||
"created_at": "2025-03-31T12:34:56",
|
||||
|
|
@ -411,6 +498,8 @@ class DocsStatusesResponse(BaseModel):
|
|||
{
|
||||
"id": "doc_123",
|
||||
"content_summary": "Pending document",
|
||||
"scheme_name": "lightrag",
|
||||
"multimodal_content": [],
|
||||
"content_length": 5000,
|
||||
"status": "PENDING",
|
||||
"created_at": "2025-03-31T10:00:00",
|
||||
|
|
@ -426,6 +515,8 @@ class DocsStatusesResponse(BaseModel):
|
|||
{
|
||||
"id": "doc_456",
|
||||
"content_summary": "Processed document",
|
||||
"scheme_name": "lightrag",
|
||||
"multimodal_content": [],
|
||||
"content_length": 8000,
|
||||
"status": "PROCESSED",
|
||||
"created_at": "2025-03-31T09:00:00",
|
||||
|
|
@ -779,7 +870,7 @@ def get_unique_filename_in_enqueued(target_dir: Path, original_name: str) -> str
|
|||
|
||||
|
||||
async def pipeline_enqueue_file(
|
||||
rag: LightRAG, file_path: Path, track_id: str = None
|
||||
rag: LightRAG, file_path: Path, track_id: str = None, scheme_name: str = None
|
||||
) -> tuple[bool, str]:
|
||||
"""Add a file to the queue for processing
|
||||
|
||||
|
|
@ -787,6 +878,8 @@ async def pipeline_enqueue_file(
|
|||
rag: LightRAG instance
|
||||
file_path: Path to the saved file
|
||||
track_id: Optional tracking ID, if not provided will be generated
|
||||
scheme_name (str, optional): Processing scheme name for categorization.
|
||||
Defaults to None
|
||||
Returns:
|
||||
tuple: (success: bool, track_id: str)
|
||||
"""
|
||||
|
|
@ -1159,7 +1252,10 @@ async def pipeline_enqueue_file(
|
|||
|
||||
try:
|
||||
await rag.apipeline_enqueue_documents(
|
||||
content, file_paths=file_path.name, track_id=track_id
|
||||
content,
|
||||
file_paths=file_path.name,
|
||||
track_id=track_id,
|
||||
scheme_name=scheme_name,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
|
|
@ -1243,17 +1339,21 @@ async def pipeline_enqueue_file(
|
|||
logger.error(f"Error deleting file {file_path}: {str(e)}")
|
||||
|
||||
|
||||
async def pipeline_index_file(rag: LightRAG, file_path: Path, track_id: str = None):
|
||||
async def pipeline_index_file(
|
||||
rag: LightRAG, file_path: Path, track_id: str = None, scheme_name: str = None
|
||||
):
|
||||
"""Index a file with track_id
|
||||
|
||||
Args:
|
||||
rag: LightRAG instance
|
||||
file_path: Path to the saved file
|
||||
track_id: Optional tracking ID
|
||||
scheme_name (str, optional): Processing scheme name for categorization.
|
||||
Defaults to None
|
||||
"""
|
||||
try:
|
||||
success, returned_track_id = await pipeline_enqueue_file(
|
||||
rag, file_path, track_id
|
||||
rag, file_path, track_id, scheme_name
|
||||
)
|
||||
if success:
|
||||
await rag.apipeline_process_enqueue_documents()
|
||||
|
|
@ -1264,7 +1364,7 @@ async def pipeline_index_file(rag: LightRAG, file_path: Path, track_id: str = No
|
|||
|
||||
|
||||
async def pipeline_index_files(
|
||||
rag: LightRAG, file_paths: List[Path], track_id: str = None
|
||||
rag: LightRAG, file_paths: List[Path], track_id: str = None, scheme_name: str = None
|
||||
):
|
||||
"""Index multiple files sequentially to avoid high CPU load
|
||||
|
||||
|
|
@ -1272,6 +1372,8 @@ async def pipeline_index_files(
|
|||
rag: LightRAG instance
|
||||
file_paths: Paths to the files to index
|
||||
track_id: Optional tracking ID to pass to all files
|
||||
scheme_name (str, optional): Processing scheme name for categorization.
|
||||
Defaults to None
|
||||
"""
|
||||
if not file_paths:
|
||||
return
|
||||
|
|
@ -1285,7 +1387,9 @@ async def pipeline_index_files(
|
|||
|
||||
# Process files sequentially with track_id
|
||||
for file_path in sorted_file_paths:
|
||||
success, _ = await pipeline_enqueue_file(rag, file_path, track_id)
|
||||
success, _ = await pipeline_enqueue_file(
|
||||
rag, file_path, track_id, scheme_name
|
||||
)
|
||||
if success:
|
||||
enqueued = True
|
||||
|
||||
|
|
@ -1297,6 +1401,61 @@ async def pipeline_index_files(
|
|||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
async def pipeline_index_files_raganything(
|
||||
rag_anything: RAGAnything,
|
||||
file_paths: List[Path],
|
||||
scheme_name: str = None,
|
||||
parser: str = None,
|
||||
source: str = None,
|
||||
):
|
||||
"""Index multiple files using RAGAnything framework for multimodal processing.
|
||||
|
||||
Args:
|
||||
rag_anything (RAGAnything): RAGAnything instance for multimodal document processing
|
||||
file_paths (List[Path]): List of file paths to be processed
|
||||
track_id (str, optional): Tracking ID for batch monitoring. Defaults to None.
|
||||
scheme_name (str, optional): Processing scheme name for categorization.
|
||||
Defaults to None.
|
||||
parser (str, optional): Document extraction tool to use.
|
||||
Defaults to None.
|
||||
source (str, optional): The model source used by Mineru.
|
||||
Defaults to None.
|
||||
|
||||
Note:
|
||||
- Uses RAGAnything's process_document_complete_lightrag_api method for each file
|
||||
- Supports multimodal content processing (images, tables, equations)
|
||||
- Files are processed with "auto" parse method and "modelscope" source
|
||||
- Output is saved to "./output" directory
|
||||
- Errors are logged but don't stop processing of remaining files
|
||||
"""
|
||||
if not file_paths:
|
||||
return
|
||||
|
||||
try:
|
||||
# Use get_pinyin_sort_key for Chinese pinyin sorting
|
||||
sorted_file_paths = sorted(
|
||||
file_paths, key=lambda p: get_pinyin_sort_key(str(p))
|
||||
)
|
||||
|
||||
# Process files sequentially with track_id
|
||||
for file_path in sorted_file_paths:
|
||||
success = await rag_anything.process_document_complete_lightrag_api(
|
||||
file_path=str(file_path),
|
||||
output_dir="./output",
|
||||
parse_method="auto",
|
||||
scheme_name=scheme_name,
|
||||
parser=parser,
|
||||
source=source,
|
||||
)
|
||||
if success:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error indexing files: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
|
||||
async def pipeline_index_texts(
|
||||
rag: LightRAG,
|
||||
texts: List[str],
|
||||
|
|
@ -1326,24 +1485,67 @@ async def pipeline_index_texts(
|
|||
|
||||
|
||||
async def run_scanning_process(
|
||||
rag: LightRAG, doc_manager: DocumentManager, track_id: str = None
|
||||
rag: LightRAG,
|
||||
rag_anything: RAGAnything,
|
||||
doc_manager: DocumentManager,
|
||||
track_id: str = None,
|
||||
schemeConfig=None,
|
||||
):
|
||||
"""Background task to scan and index documents
|
||||
|
||||
Args:
|
||||
rag: LightRAG instance
|
||||
rag_anythingL: RAGAnything instance
|
||||
doc_manager: DocumentManager instance
|
||||
track_id: Optional tracking ID to pass to all scanned files
|
||||
schemeConfig: Scanning scheme configuration.
|
||||
Defaults to None
|
||||
"""
|
||||
try:
|
||||
new_files = doc_manager.scan_directory_for_new_files()
|
||||
total_files = len(new_files)
|
||||
logger.info(f"Found {total_files} files to index.")
|
||||
|
||||
from lightrag.kg.shared_storage import get_namespace_data
|
||||
|
||||
pipeline_status = await get_namespace_data("pipeline_status")
|
||||
is_pipeline_scan_busy = pipeline_status.get("scan_disabled", False)
|
||||
is_pipeline_busy = pipeline_status.get("busy", False)
|
||||
|
||||
scheme_name = schemeConfig.framework
|
||||
extractor = schemeConfig.extractor
|
||||
modelSource = schemeConfig.modelSource
|
||||
|
||||
if new_files:
|
||||
# Process all files at once with track_id
|
||||
await pipeline_index_files(rag, new_files, track_id)
|
||||
logger.info(f"Scanning process completed: {total_files} files Processed.")
|
||||
if is_pipeline_busy:
|
||||
logger.info(
|
||||
"Pipe is currently busy, skipping processing to avoid conflicts..."
|
||||
)
|
||||
return
|
||||
if is_pipeline_scan_busy:
|
||||
logger.info(
|
||||
"Pipe is currently busy, skipping processing to avoid conflicts..."
|
||||
)
|
||||
return
|
||||
if scheme_name == "lightrag":
|
||||
await pipeline_index_files(
|
||||
rag, new_files, track_id, scheme_name=scheme_name
|
||||
)
|
||||
logger.info(
|
||||
f"Scanning process completed with lightrag: {total_files} files Processed."
|
||||
)
|
||||
elif scheme_name == "raganything":
|
||||
await pipeline_index_files_raganything(
|
||||
rag_anything,
|
||||
new_files,
|
||||
scheme_name=scheme_name,
|
||||
parser=extractor,
|
||||
source=modelSource,
|
||||
)
|
||||
logger.info(
|
||||
f"Scanning process completed with raganything: {total_files} files Processed."
|
||||
)
|
||||
else:
|
||||
# No new files to index, check if there are any documents in the queue
|
||||
logger.info(
|
||||
|
|
@ -1554,15 +1756,250 @@ async def background_delete_documents(
|
|||
|
||||
|
||||
def create_document_routes(
|
||||
rag: LightRAG, doc_manager: DocumentManager, api_key: Optional[str] = None
|
||||
rag: LightRAG,
|
||||
rag_anything: RAGAnything,
|
||||
doc_manager: DocumentManager,
|
||||
api_key: Optional[str] = None,
|
||||
):
|
||||
# Create combined auth dependency for document routes
|
||||
combined_auth = get_combined_auth_dependency(api_key)
|
||||
|
||||
@router.get(
|
||||
"/schemes",
|
||||
response_model=SchemesResponse,
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def get_all_schemes():
|
||||
"""Get all available processing schemes.
|
||||
|
||||
Retrieves the complete list of processing schemes from the schemes.json file.
|
||||
Each scheme defines a processing framework (lightrag/raganything) and
|
||||
optional extractor configuration (mineru/docling).
|
||||
|
||||
Returns:
|
||||
SchemesResponse: Response containing:
|
||||
- status (str): Operation status ("success")
|
||||
- message (str): Success message
|
||||
- data (List[Dict]): List of all available schemes with their configurations
|
||||
|
||||
Raises:
|
||||
HTTPException: If file reading fails or JSON parsing errors occur (500)
|
||||
"""
|
||||
SCHEMES_FILE = Path("./examples/schemes.json")
|
||||
|
||||
if SCHEMES_FILE.exists():
|
||||
with open(SCHEMES_FILE, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
current_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
current_data = []
|
||||
else:
|
||||
current_data = []
|
||||
SCHEMES_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(SCHEMES_FILE, "w") as f:
|
||||
json.dump(current_data, f)
|
||||
|
||||
return SchemesResponse(
|
||||
status="success",
|
||||
message="Schemes retrieved successfully",
|
||||
data=current_data,
|
||||
)
|
||||
|
||||
@router.post(
|
||||
"/schemes",
|
||||
response_model=SchemesResponse,
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def save_schemes(schemes: list[Scheme_include_id]):
|
||||
"""Save/update processing schemes in batch.
|
||||
|
||||
Updates existing schemes with new configuration data. This endpoint performs
|
||||
a partial update by modifying existing schemes based on their IDs while
|
||||
preserving other schemes in the file.
|
||||
|
||||
Args:
|
||||
schemes (list[Scheme_include_id]): List of schemes to update, each containing:
|
||||
- id (int): Unique identifier of the scheme to update
|
||||
- name (str): Display name for the scheme
|
||||
- config (SchemeConfig): Configuration object with framework and extractor settings
|
||||
|
||||
Returns:
|
||||
SchemesResponse: Response containing:
|
||||
- status (str): Operation status ("success")
|
||||
- message (str): Success message with count of saved schemes
|
||||
- data (List[Dict]): Updated list of all schemes after modification
|
||||
|
||||
Raises:
|
||||
HTTPException: If file operations fail or JSON processing errors occur (500)
|
||||
"""
|
||||
try:
|
||||
SCHEMES_FILE = Path("./examples/schemes.json")
|
||||
|
||||
if SCHEMES_FILE.exists():
|
||||
with open(SCHEMES_FILE, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
current_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
current_data = []
|
||||
else:
|
||||
current_data = []
|
||||
|
||||
updated_item = {
|
||||
"id": schemes[0].id,
|
||||
"name": schemes[0].name,
|
||||
"config": {
|
||||
"framework": schemes[0].config.framework,
|
||||
"extractor": schemes[0].config.extractor,
|
||||
"modelSource": schemes[0].config.modelSource,
|
||||
},
|
||||
}
|
||||
# 保存新方案
|
||||
for item in current_data:
|
||||
if item["id"] == updated_item["id"]:
|
||||
item["name"] = updated_item["name"]
|
||||
item["config"]["framework"] = updated_item["config"]["framework"]
|
||||
item["config"]["extractor"] = updated_item["config"]["extractor"]
|
||||
item["config"]["modelSource"] = updated_item["config"][
|
||||
"modelSource"
|
||||
]
|
||||
break
|
||||
|
||||
# 写回文件
|
||||
with open(SCHEMES_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(current_data, f, indent=4)
|
||||
|
||||
# 返回响应(从文件重新读取确保一致性)
|
||||
with open(SCHEMES_FILE, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
return SchemesResponse(
|
||||
status="success",
|
||||
message=f"Successfully saved {len(schemes)} schemes",
|
||||
data=data,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post(
|
||||
"/schemes/add",
|
||||
response_model=Scheme_include_id,
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def add_scheme(scheme: Scheme):
|
||||
"""Add a new processing scheme.
|
||||
|
||||
Creates a new processing scheme with auto-generated ID and saves it to the
|
||||
schemes configuration file. The new scheme will be available for document
|
||||
processing operations.
|
||||
|
||||
Args:
|
||||
scheme (Scheme): New scheme to add, containing:
|
||||
- name (str): Display name for the scheme
|
||||
- config (SchemeConfig): Configuration with framework and extractor settings
|
||||
|
||||
Returns:
|
||||
Scheme_include_id: The created scheme with auto-generated ID, containing:
|
||||
- id (int): Auto-generated unique identifier
|
||||
- name (str): Display name of the scheme
|
||||
- config (SchemeConfig): Processing configuration
|
||||
|
||||
Raises:
|
||||
HTTPException: If file operations fail or ID generation conflicts occur (500)
|
||||
"""
|
||||
try:
|
||||
SCHEMES_FILE = Path("./examples/schemes.json")
|
||||
|
||||
if SCHEMES_FILE.exists():
|
||||
with open(SCHEMES_FILE, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
current_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
current_data = []
|
||||
else:
|
||||
current_data = []
|
||||
|
||||
# 生成新ID(简单实现,实际项目应该用数据库自增ID)
|
||||
new_id = uuid.uuid4().int >> 96 # 生成一个较小的整数ID
|
||||
while new_id in current_data:
|
||||
new_id = uuid.uuid4().int >> 96
|
||||
|
||||
new_scheme = {
|
||||
"id": new_id,
|
||||
"name": scheme.name,
|
||||
"config": {
|
||||
"framework": scheme.config.framework,
|
||||
"extractor": scheme.config.extractor,
|
||||
"modelSource": scheme.config.modelSource,
|
||||
},
|
||||
}
|
||||
|
||||
current_data.append(new_scheme)
|
||||
|
||||
with open(SCHEMES_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(current_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return new_scheme
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete(
|
||||
"/schemes/{scheme_id}",
|
||||
response_model=Dict[str, str],
|
||||
dependencies=[Depends(combined_auth)],
|
||||
)
|
||||
async def delete_scheme(scheme_id: int):
|
||||
"""Delete a specific processing scheme by ID.
|
||||
|
||||
Removes a processing scheme from the configuration file. Once deleted,
|
||||
the scheme will no longer be available for document processing operations.
|
||||
|
||||
Args:
|
||||
scheme_id (int): Unique identifier of the scheme to delete
|
||||
|
||||
Returns:
|
||||
Dict[str, str]: Success message containing:
|
||||
- message (str): Confirmation message with the deleted scheme ID
|
||||
|
||||
Raises:
|
||||
HTTPException:
|
||||
- 404: If the scheme with the specified ID is not found
|
||||
- 500: If file operations fail or other errors occur
|
||||
"""
|
||||
try:
|
||||
SCHEMES_FILE = Path("./examples/schemes.json")
|
||||
|
||||
if SCHEMES_FILE.exists():
|
||||
with open(SCHEMES_FILE, "r", encoding="utf-8") as f:
|
||||
try:
|
||||
current_data = json.load(f)
|
||||
except json.JSONDecodeError:
|
||||
current_data = []
|
||||
else:
|
||||
current_data = []
|
||||
|
||||
current_data_dict = {scheme["id"]: scheme for scheme in current_data}
|
||||
|
||||
if scheme_id not in current_data_dict: # 直接检查 id 是否存在
|
||||
raise HTTPException(status_code=404, detail="Scheme not found")
|
||||
|
||||
for i, scheme in enumerate(current_data):
|
||||
if scheme["id"] == scheme_id:
|
||||
del current_data[i] # 直接删除列表中的元素
|
||||
break
|
||||
|
||||
with open(SCHEMES_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(current_data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
return {"message": f"Scheme {scheme_id} deleted successfully"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@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(
|
||||
request: ScanRequest, background_tasks: BackgroundTasks
|
||||
):
|
||||
"""
|
||||
Trigger the scanning process for new documents.
|
||||
|
||||
|
|
@ -1577,7 +2014,14 @@ def create_document_routes(
|
|||
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)
|
||||
background_tasks.add_task(
|
||||
run_scanning_process,
|
||||
rag,
|
||||
rag_anything,
|
||||
doc_manager,
|
||||
track_id,
|
||||
schemeConfig=request.schemeConfig,
|
||||
)
|
||||
return ScanResponse(
|
||||
status="scanning_started",
|
||||
message="Scanning process has been initiated in the background",
|
||||
|
|
@ -1588,7 +2032,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(...),
|
||||
schemeId: str = Form(...),
|
||||
):
|
||||
"""
|
||||
Upload a file to the input directory and index it.
|
||||
|
|
@ -1599,7 +2045,9 @@ 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.
|
||||
file (UploadFile): The file to be uploaded. It must have an allowed extension
|
||||
schemeId (str): ID of the processing scheme to use for this file. The scheme
|
||||
determines whether to use LightRAG or RAGAnything framework for processing.
|
||||
|
||||
Returns:
|
||||
InsertResponse: A response object containing the upload status and a message.
|
||||
|
|
@ -1632,8 +2080,62 @@ 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)
|
||||
def load_config():
|
||||
try:
|
||||
SCHEMES_FILE = Path("./examples/schemes.json")
|
||||
with open(SCHEMES_FILE, "r") as f:
|
||||
schemes = json.load(f)
|
||||
for scheme in schemes:
|
||||
if str(scheme.get("id")) == schemeId:
|
||||
return scheme.get("config", {})
|
||||
return {}
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Failed to load config for scheme {schemeId}: {str(e)}"
|
||||
)
|
||||
return {}
|
||||
|
||||
config = load_config()
|
||||
current_framework = config.get("framework")
|
||||
current_extractor = config.get("extractor")
|
||||
current_modelSource = config.get("modelSource")
|
||||
doc_pre_id = f"doc-pre-{safe_filename}"
|
||||
|
||||
if current_framework and current_framework == "lightrag":
|
||||
# Add to background tasks and get track_id
|
||||
background_tasks.add_task(
|
||||
pipeline_index_file,
|
||||
rag,
|
||||
file_path,
|
||||
track_id,
|
||||
scheme_name=current_framework,
|
||||
)
|
||||
else:
|
||||
background_tasks.add_task(
|
||||
rag_anything.process_document_complete_lightrag_api,
|
||||
file_path=str(file_path),
|
||||
output_dir="./output",
|
||||
parse_method="auto",
|
||||
scheme_name=current_framework,
|
||||
parser=current_extractor,
|
||||
source=current_modelSource,
|
||||
)
|
||||
|
||||
await rag.doc_status.upsert(
|
||||
{
|
||||
doc_pre_id: {
|
||||
"status": DocStatus.READY,
|
||||
"content": "",
|
||||
"content_summary": "",
|
||||
"multimodal_content": [],
|
||||
"scheme_name": current_framework,
|
||||
"content_length": 0,
|
||||
"created_at": "",
|
||||
"updated_at": "",
|
||||
"file_path": safe_filename,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return InsertResponse(
|
||||
status="success",
|
||||
|
|
@ -1854,6 +2356,42 @@ def create_document_routes(
|
|||
f"Successfully dropped all {storage_success_count} storage components"
|
||||
)
|
||||
|
||||
# Clean all parse_cache entries after successful storage drops
|
||||
if storage_success_count > 0:
|
||||
try:
|
||||
if "history_messages" in pipeline_status:
|
||||
pipeline_status["history_messages"].append(
|
||||
"Cleaning parse_cache entries"
|
||||
)
|
||||
|
||||
parse_cache_result = await rag.aclean_all_parse_cache()
|
||||
if parse_cache_result.get("error"):
|
||||
cache_error_msg = f"Warning: Failed to clean parse_cache: {parse_cache_result['error']}"
|
||||
logger.warning(cache_error_msg)
|
||||
if "history_messages" in pipeline_status:
|
||||
pipeline_status["history_messages"].append(cache_error_msg)
|
||||
else:
|
||||
deleted_count = parse_cache_result.get("deleted_count", 0)
|
||||
if deleted_count > 0:
|
||||
cache_success_msg = f"Successfully cleaned {deleted_count} parse_cache entries"
|
||||
logger.info(cache_success_msg)
|
||||
if "history_messages" in pipeline_status:
|
||||
pipeline_status["history_messages"].append(
|
||||
cache_success_msg
|
||||
)
|
||||
else:
|
||||
cache_empty_msg = "No parse_cache entries to clean"
|
||||
logger.info(cache_empty_msg)
|
||||
if "history_messages" in pipeline_status:
|
||||
pipeline_status["history_messages"].append(
|
||||
cache_empty_msg
|
||||
)
|
||||
except Exception as cache_error:
|
||||
cache_error_msg = f"Warning: Exception while cleaning parse_cache: {str(cache_error)}"
|
||||
logger.warning(cache_error_msg)
|
||||
if "history_messages" in pipeline_status:
|
||||
pipeline_status["history_messages"].append(cache_error_msg)
|
||||
|
||||
# If all storage operations failed, return error status and don't proceed with file deletion
|
||||
if storage_success_count == 0 and storage_error_count > 0:
|
||||
error_message = "All storage drop operations failed. Aborting document clearing process."
|
||||
|
|
@ -2025,7 +2563,7 @@ def create_document_routes(
|
|||
Get the status of all documents in the system.
|
||||
|
||||
This endpoint retrieves the current status of all documents, grouped by their
|
||||
processing status (PENDING, PROCESSING, PROCESSED, FAILED).
|
||||
processing status (READY, HANDLING, PENDING, PROCESSING, PROCESSED, FAILED).
|
||||
|
||||
Returns:
|
||||
DocsStatusesResponse: A response object containing a dictionary where keys are
|
||||
|
|
@ -2037,6 +2575,8 @@ def create_document_routes(
|
|||
"""
|
||||
try:
|
||||
statuses = (
|
||||
DocStatus.READY,
|
||||
DocStatus.HANDLING,
|
||||
DocStatus.PENDING,
|
||||
DocStatus.PROCESSING,
|
||||
DocStatus.PROCESSED,
|
||||
|
|
@ -2057,6 +2597,7 @@ def create_document_routes(
|
|||
DocStatusResponse(
|
||||
id=doc_id,
|
||||
content_summary=doc_status.content_summary,
|
||||
multimodal_content=doc_status.multimodal_content,
|
||||
content_length=doc_status.content_length,
|
||||
status=doc_status.status,
|
||||
created_at=format_datetime(doc_status.created_at),
|
||||
|
|
@ -2066,6 +2607,7 @@ def create_document_routes(
|
|||
error_msg=doc_status.error_msg,
|
||||
metadata=doc_status.metadata,
|
||||
file_path=doc_status.file_path,
|
||||
scheme_name=doc_status.scheme_name,
|
||||
)
|
||||
)
|
||||
return response
|
||||
|
|
@ -2402,6 +2944,8 @@ def create_document_routes(
|
|||
error_msg=doc.error_msg,
|
||||
metadata=doc.metadata,
|
||||
file_path=doc.file_path,
|
||||
scheme_name=doc.scheme_name,
|
||||
multimodal_content=doc.multimodal_content,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
1
lightrag/api/webui/assets/_basePickBy-C1BlOoDW.js
generated
Normal file
1
lightrag/api/webui/assets/_basePickBy-C1BlOoDW.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{e as o,c as l,g as b,k as O,h as P,j as p,l as w,m as c,n as v,t as A,o as N}from"./_baseUniq-BZ_JDEKn.js";import{a_ as g,aw as _,a$ as $,b0 as E,b1 as F,b2 as x,b3 as M,b4 as y,b5 as B,b6 as T}from"./mermaid-vendor-CpW20EHd.js";var S=/\s/;function G(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var H=/^\s+/;function L(n){return n&&n.slice(0,G(n)+1).replace(H,"")}var m=NaN,R=/^[-+]0x[0-9a-f]+$/i,q=/^0b[01]+$/i,z=/^0o[0-7]+$/i,C=parseInt;function K(n){if(typeof n=="number")return n;if(o(n))return m;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=L(n);var t=q.test(n);return t||z.test(n)?C(n.slice(2),t?2:8):R.test(n)?m:+n}var W=1/0,X=17976931348623157e292;function Y(n){if(!n)return n===0?n:0;if(n=K(n),n===W||n===-1/0){var r=n<0?-1:1;return r*X}return n===n?n:0}function D(n){var r=Y(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?l(n):[]}var I=Object.prototype,J=I.hasOwnProperty,dn=_(function(n,r){n=Object(n);var t=-1,e=r.length,i=e>2?r[2]:void 0;for(i&&$(r[0],r[1],i)&&(e=1);++t<e;)for(var f=r[t],a=E(f),s=-1,d=a.length;++s<d;){var u=a[s],h=n[u];(h===void 0||F(h,I[u])&&!J.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function Q(n){return function(r,t,e){var i=Object(r);if(!x(r)){var f=b(t);r=O(r),t=function(s){return f(i[s],s,i)}}var a=n(r,t,e);return a>-1?i[f?r[a]:a]:void 0}}var U=Math.max;function Z(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var i=t==null?0:D(t);return i<0&&(i=U(e+i,0)),P(n,b(r),i)}var hn=Q(Z);function V(n,r){var t=-1,e=x(n)?Array(n.length):[];return p(n,function(i,f,a){e[++t]=r(i,f,a)}),e}function gn(n,r){var t=M(n)?w:V;return t(n,b(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function bn(n,r){return n!=null&&c(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,i=n.length;++e<i;){var f=n[e],a=r(f);if(a!=null&&(s===void 0?a===a&&!o(a):t(a,s)))var s=a,d=f}return d}function mn(n){return n&&n.length?tn(n,y,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=v(r,n);for(var i=-1,f=r.length,a=f-1,s=n;s!=null&&++i<f;){var d=A(r[i]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(i!=a){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[i+1])?[]:{})}T(s,d,u),s=s[d]}return n}function on(n,r,t){for(var e=-1,i=r.length,f={};++e<i;){var a=r[e],s=N(n,a);t(s,a)&&an(f,v(a,n),s)}return f}export{rn as a,tn as b,V as c,on as d,mn as e,fn as f,hn as g,bn as h,dn as i,D as j,un as l,gn as m,Y as t};
|
||||
1
lightrag/api/webui/assets/_baseUniq-BZ_JDEKn.js
generated
Normal file
1
lightrag/api/webui/assets/_baseUniq-BZ_JDEKn.js
generated
Normal file
File diff suppressed because one or more lines are too long
36
lightrag/api/webui/assets/architectureDiagram-SUXI7LT5-BT0syfmv.js
generated
Normal file
36
lightrag/api/webui/assets/architectureDiagram-SUXI7LT5-BT0syfmv.js
generated
Normal file
File diff suppressed because one or more lines are too long
122
lightrag/api/webui/assets/blockDiagram-6J76NXCF-Cw1GmT-O.js
generated
Normal file
122
lightrag/api/webui/assets/blockDiagram-6J76NXCF-Cw1GmT-O.js
generated
Normal file
File diff suppressed because one or more lines are too long
10
lightrag/api/webui/assets/c4Diagram-6F6E4RAY-DXwAY8mp.js
generated
Normal file
10
lightrag/api/webui/assets/c4Diagram-6F6E4RAY-DXwAY8mp.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/chunk-353BL4L5-0V1KVYyT.js
generated
Normal file
1
lightrag/api/webui/assets/chunk-353BL4L5-0V1KVYyT.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{_ as l}from"./mermaid-vendor-CpW20EHd.js";function m(e,c){var i,t,o;e.accDescr&&((i=c.setAccDescription)==null||i.call(c,e.accDescr)),e.accTitle&&((t=c.setAccTitle)==null||t.call(c,e.accTitle)),e.title&&((o=c.setDiagramTitle)==null||o.call(c,e.title))}l(m,"populateCommonDb");export{m as p};
|
||||
1
lightrag/api/webui/assets/chunk-67H74DCK-bSLGaG_c.js
generated
Normal file
1
lightrag/api/webui/assets/chunk-67H74DCK-bSLGaG_c.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{_ as n,a2 as x,j as l}from"./mermaid-vendor-CpW20EHd.js";var c=n((a,t)=>{const e=a.append("rect");if(e.attr("x",t.x),e.attr("y",t.y),e.attr("fill",t.fill),e.attr("stroke",t.stroke),e.attr("width",t.width),e.attr("height",t.height),t.name&&e.attr("name",t.name),t.rx&&e.attr("rx",t.rx),t.ry&&e.attr("ry",t.ry),t.attrs!==void 0)for(const r in t.attrs)e.attr(r,t.attrs[r]);return t.class&&e.attr("class",t.class),e},"drawRect"),d=n((a,t)=>{const e={x:t.startx,y:t.starty,width:t.stopx-t.startx,height:t.stopy-t.starty,fill:t.fill,stroke:t.stroke,class:"rect"};c(a,e).lower()},"drawBackgroundRect"),g=n((a,t)=>{const e=t.text.replace(x," "),r=a.append("text");r.attr("x",t.x),r.attr("y",t.y),r.attr("class","legend"),r.style("text-anchor",t.anchor),t.class&&r.attr("class",t.class);const s=r.append("tspan");return s.attr("x",t.x+t.textMargin*2),s.text(e),r},"drawText"),h=n((a,t,e,r)=>{const s=a.append("image");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",i)},"drawImage"),m=n((a,t,e,r)=>{const s=a.append("use");s.attr("x",t),s.attr("y",e);const i=l.sanitizeUrl(r);s.attr("xlink:href",`#${i}`)},"drawEmbeddedImage"),y=n(()=>({x:0,y:0,width:100,height:100,fill:"#EDF2AE",stroke:"#666",anchor:"start",rx:0,ry:0}),"getNoteRect"),p=n(()=>({x:0,y:0,width:100,height:100,"text-anchor":"start",style:"#666",textMargin:0,rx:0,ry:0,tspan:!0}),"getTextObj");export{d as a,p as b,m as c,c as d,h as e,g as f,y as g};
|
||||
1
lightrag/api/webui/assets/chunk-AACKK3MU-DzHRGgvZ.js
generated
Normal file
1
lightrag/api/webui/assets/chunk-AACKK3MU-DzHRGgvZ.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{_ as s}from"./mermaid-vendor-CpW20EHd.js";var t,e=(t=class{constructor(i){this.init=i,this.records=this.init()}reset(){this.records=this.init()}},s(t,"ImperativeState"),t);export{e as I};
|
||||
1
lightrag/api/webui/assets/chunk-BFAMUDN2-CHovHiOg.js
generated
Normal file
1
lightrag/api/webui/assets/chunk-BFAMUDN2-CHovHiOg.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{_ as a,d as o}from"./mermaid-vendor-CpW20EHd.js";var d=a((t,e)=>{let n;return e==="sandbox"&&(n=o("#i"+t)),(e==="sandbox"?o(n.nodes()[0].contentDocument.body):o("body")).select(`[id="${t}"]`)},"getDiagramElement");export{d as g};
|
||||
15
lightrag/api/webui/assets/chunk-E2GYISFI-DgamQYak.js
generated
Normal file
15
lightrag/api/webui/assets/chunk-E2GYISFI-DgamQYak.js
generated
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import{_ as e}from"./mermaid-vendor-CpW20EHd.js";var l=e(()=>`
|
||||
/* Font Awesome icon styling - consolidated */
|
||||
.label-icon {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
overflow: visible;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
|
||||
.node .label-icon path {
|
||||
fill: currentColor;
|
||||
stroke: revert;
|
||||
stroke-width: revert;
|
||||
}
|
||||
`,"getIconStyles");export{l as g};
|
||||
220
lightrag/api/webui/assets/chunk-OW32GOEJ-DF1Nd_2F.js
generated
Normal file
220
lightrag/api/webui/assets/chunk-OW32GOEJ-DF1Nd_2F.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/chunk-SKB7J2MH-CqH3ZkpA.js
generated
Normal file
1
lightrag/api/webui/assets/chunk-SKB7J2MH-CqH3ZkpA.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{_ as a,e as w,l as x}from"./mermaid-vendor-CpW20EHd.js";var d=a((e,t,i,o)=>{e.attr("class",i);const{width:r,height:h,x:n,y:c}=u(e,t);w(e,h,r,o);const s=l(n,c,r,h,t);e.attr("viewBox",s),x.debug(`viewBox configured: ${s} with padding: ${t}`)},"setupViewPortForSVG"),u=a((e,t)=>{var o;const i=((o=e.node())==null?void 0:o.getBBox())||{width:0,height:0,x:0,y:0};return{width:i.width+t*2,height:i.height+t*2,x:i.x,y:i.y}},"calculateDimensionsWithPadding"),l=a((e,t,i,o,r)=>`${e-r} ${t-r} ${i} ${o}`,"createViewBox");export{d as s};
|
||||
165
lightrag/api/webui/assets/chunk-SZ463SBG-CG1c8KxJ.js
generated
Normal file
165
lightrag/api/webui/assets/chunk-SZ463SBG-CG1c8KxJ.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/classDiagram-M3E45YP4-DbVgy_9-.js
generated
Normal file
1
lightrag/api/webui/assets/classDiagram-M3E45YP4-DbVgy_9-.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{s as a,c as s,a as e,C as t}from"./chunk-SZ463SBG-CG1c8KxJ.js";import{_ as i}from"./mermaid-vendor-CpW20EHd.js";import"./chunk-E2GYISFI-DgamQYak.js";import"./chunk-BFAMUDN2-CHovHiOg.js";import"./chunk-SKB7J2MH-CqH3ZkpA.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var c={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{c as diagram};
|
||||
1
lightrag/api/webui/assets/classDiagram-v2-YAWTLIQI-DbVgy_9-.js
generated
Normal file
1
lightrag/api/webui/assets/classDiagram-v2-YAWTLIQI-DbVgy_9-.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{s as a,c as s,a as e,C as t}from"./chunk-SZ463SBG-CG1c8KxJ.js";import{_ as i}from"./mermaid-vendor-CpW20EHd.js";import"./chunk-E2GYISFI-DgamQYak.js";import"./chunk-BFAMUDN2-CHovHiOg.js";import"./chunk-SKB7J2MH-CqH3ZkpA.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var c={parser:e,get db(){return new t},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{c as diagram};
|
||||
1
lightrag/api/webui/assets/clone-CDvVvGlj.js
generated
Normal file
1
lightrag/api/webui/assets/clone-CDvVvGlj.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{b as r}from"./_baseUniq-BZ_JDEKn.js";var e=4;function a(o){return r(o,e)}export{a as c};
|
||||
4
lightrag/api/webui/assets/dagre-JOIXM2OF-DqhHM8LL.js
generated
Normal file
4
lightrag/api/webui/assets/dagre-JOIXM2OF-DqhHM8LL.js
generated
Normal file
File diff suppressed because one or more lines are too long
24
lightrag/api/webui/assets/diagram-5UYTHUR4-BB2kofV9.js
generated
Normal file
24
lightrag/api/webui/assets/diagram-5UYTHUR4-BB2kofV9.js
generated
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import{p as y}from"./chunk-353BL4L5-0V1KVYyT.js";import{_ as l,s as B,g as S,t as z,q as F,a as P,b as E,F as v,K as W,e as T,z as D,G as _,H as A,l as w}from"./mermaid-vendor-CpW20EHd.js";import{p as N}from"./treemap-75Q7IDZK-CSah7hvo.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-BZ_JDEKn.js";import"./_basePickBy-C1BlOoDW.js";import"./clone-CDvVvGlj.js";var x={packet:[]},m=structuredClone(x),L=A.packet,Y=l(()=>{const t=v({...L,..._().packet});return t.showBits&&(t.paddingY+=10),t},"getConfig"),G=l(()=>m.packet,"getPacket"),H=l(t=>{t.length>0&&m.packet.push(t)},"pushWord"),I=l(()=>{D(),m=structuredClone(x)},"clear"),u={pushWord:H,getPacket:G,getConfig:Y,clear:I,setAccTitle:E,getAccTitle:P,setDiagramTitle:F,getDiagramTitle:z,getAccDescription:S,setAccDescription:B},K=1e4,M=l(t=>{y(t,u);let e=-1,o=[],n=1;const{bitsPerRow:i}=u.getConfig();for(let{start:a,end:r,bits:c,label:f}of t.blocks){if(a!==void 0&&r!==void 0&&r<a)throw new Error(`Packet block ${a} - ${r} is invalid. End must be greater than start.`);if(a??(a=e+1),a!==e+1)throw new Error(`Packet block ${a} - ${r??a} is not contiguous. It should start from ${e+1}.`);if(c===0)throw new Error(`Packet block ${a} is invalid. Cannot have a zero bit field.`);for(r??(r=a+(c??1)-1),c??(c=r-a+1),e=r,w.debug(`Packet block ${a} - ${e} with label ${f}`);o.length<=i+1&&u.getPacket().length<K;){const[d,p]=O({start:a,end:r,bits:c,label:f},n,i);if(o.push(d),d.end+1===n*i&&(u.pushWord(o),o=[],n++),!p)break;({start:a,end:r,bits:c,label:f}=p)}}u.pushWord(o)},"populate"),O=l((t,e,o)=>{if(t.start===void 0)throw new Error("start should have been set during first phase");if(t.end===void 0)throw new Error("end should have been set during first phase");if(t.start>t.end)throw new Error(`Block start ${t.start} is greater than block end ${t.end}.`);if(t.end+1<=e*o)return[t,void 0];const n=e*o-1,i=e*o;return[{start:t.start,end:n,label:t.label,bits:n-t.start},{start:i,end:t.end,label:t.label,bits:t.end-i}]},"getNextFittingBlock"),q={parse:l(async t=>{const e=await N("packet",t);w.debug(e),M(e)},"parse")},R=l((t,e,o,n)=>{const i=n.db,a=i.getConfig(),{rowHeight:r,paddingY:c,bitWidth:f,bitsPerRow:d}=a,p=i.getPacket(),s=i.getDiagramTitle(),k=r+c,g=k*(p.length+1)-(s?0:r),b=f*d+2,h=W(e);h.attr("viewbox",`0 0 ${b} ${g}`),T(h,g,b,a.useMaxWidth);for(const[C,$]of p.entries())U(h,$,C,a);h.append("text").text(s).attr("x",b/2).attr("y",g-k/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")},"draw"),U=l((t,e,o,{rowHeight:n,paddingX:i,paddingY:a,bitWidth:r,bitsPerRow:c,showBits:f})=>{const d=t.append("g"),p=o*(n+a)+a;for(const s of e){const k=s.start%c*r+1,g=(s.end-s.start+1)*r-i;if(d.append("rect").attr("x",k).attr("y",p).attr("width",g).attr("height",n).attr("class","packetBlock"),d.append("text").attr("x",k+g/2).attr("y",p+n/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(s.label),!f)continue;const b=s.end===s.start,h=p-2;d.append("text").attr("x",k+(b?g/2:0)).attr("y",h).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",b?"middle":"start").text(s.start),b||d.append("text").attr("x",k+g).attr("y",h).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(s.end)}},"drawWord"),X={draw:R},j={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},J=l(({packet:t}={})=>{const e=v(j,t);return`
|
||||
.packetByte {
|
||||
font-size: ${e.byteFontSize};
|
||||
}
|
||||
.packetByte.start {
|
||||
fill: ${e.startByteColor};
|
||||
}
|
||||
.packetByte.end {
|
||||
fill: ${e.endByteColor};
|
||||
}
|
||||
.packetLabel {
|
||||
fill: ${e.labelColor};
|
||||
font-size: ${e.labelFontSize};
|
||||
}
|
||||
.packetTitle {
|
||||
fill: ${e.titleColor};
|
||||
font-size: ${e.titleFontSize};
|
||||
}
|
||||
.packetBlock {
|
||||
stroke: ${e.blockStrokeColor};
|
||||
stroke-width: ${e.blockStrokeWidth};
|
||||
fill: ${e.blockFillColor};
|
||||
}
|
||||
`},"styles"),lt={parser:q,db:u,renderer:X,styles:J};export{lt as diagram};
|
||||
24
lightrag/api/webui/assets/diagram-VMROVX33-BgvnouXP.js
generated
Normal file
24
lightrag/api/webui/assets/diagram-VMROVX33-BgvnouXP.js
generated
Normal file
File diff suppressed because one or more lines are too long
43
lightrag/api/webui/assets/diagram-ZTM2IBQH-B3aNf52x.js
generated
Normal file
43
lightrag/api/webui/assets/diagram-ZTM2IBQH-B3aNf52x.js
generated
Normal file
File diff suppressed because one or more lines are too long
60
lightrag/api/webui/assets/erDiagram-3M52JZNH-DCRP-5vb.js
generated
Normal file
60
lightrag/api/webui/assets/erDiagram-3M52JZNH-DCRP-5vb.js
generated
Normal file
File diff suppressed because one or more lines are too long
88
lightrag/api/webui/assets/feature-documents-22OwnQq9.js
generated
Normal file
88
lightrag/api/webui/assets/feature-documents-22OwnQq9.js
generated
Normal file
File diff suppressed because one or more lines are too long
740
lightrag/api/webui/assets/feature-graph-xUsMo1iK.js
generated
Normal file
740
lightrag/api/webui/assets/feature-graph-xUsMo1iK.js
generated
Normal file
File diff suppressed because one or more lines are too long
10
lightrag/api/webui/assets/feature-retrieval-CeceOXFg.js
generated
Normal file
10
lightrag/api/webui/assets/feature-retrieval-CeceOXFg.js
generated
Normal file
File diff suppressed because one or more lines are too long
162
lightrag/api/webui/assets/flowDiagram-KYDEHFYC-DF_nxDdv.js
generated
Normal file
162
lightrag/api/webui/assets/flowDiagram-KYDEHFYC-DF_nxDdv.js
generated
Normal file
File diff suppressed because one or more lines are too long
267
lightrag/api/webui/assets/ganttDiagram-EK5VF46D-CXC2deEB.js
generated
Normal file
267
lightrag/api/webui/assets/ganttDiagram-EK5VF46D-CXC2deEB.js
generated
Normal file
File diff suppressed because one or more lines are too long
65
lightrag/api/webui/assets/gitGraphDiagram-GW3U2K7C-CMqZFzuf.js
generated
Normal file
65
lightrag/api/webui/assets/gitGraphDiagram-GW3U2K7C-CMqZFzuf.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/graph-DPayJM68.js
generated
Normal file
1
lightrag/api/webui/assets/graph-DPayJM68.js
generated
Normal file
File diff suppressed because one or more lines are too long
3
lightrag/api/webui/assets/index-91CvGs5J.js
generated
Normal file
3
lightrag/api/webui/assets/index-91CvGs5J.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/index-BvrNHAMA.css
generated
1
lightrag/api/webui/assets/index-BvrNHAMA.css
generated
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/index-CEupRNOQ.css
generated
Normal file
1
lightrag/api/webui/assets/index-CEupRNOQ.css
generated
Normal file
File diff suppressed because one or more lines are too long
151
lightrag/api/webui/assets/index-oYZPo1xP.js
generated
Normal file
151
lightrag/api/webui/assets/index-oYZPo1xP.js
generated
Normal file
File diff suppressed because one or more lines are too long
2
lightrag/api/webui/assets/infoDiagram-LHK5PUON-BcENj9Cy.js
generated
Normal file
2
lightrag/api/webui/assets/infoDiagram-LHK5PUON-BcENj9Cy.js
generated
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import{_ as e,l as o,K as i,e as n,L as p}from"./mermaid-vendor-CpW20EHd.js";import{p as m}from"./treemap-75Q7IDZK-CSah7hvo.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-BZ_JDEKn.js";import"./_basePickBy-C1BlOoDW.js";import"./clone-CDvVvGlj.js";var g={parse:e(async r=>{const a=await m("info",r);o.debug(a)},"parse")},v={version:p.version+""},d=e(()=>v.version,"getVersion"),c={getVersion:d},l=e((r,a,s)=>{o.debug(`rendering info diagram
|
||||
`+r);const t=i(a);n(t,100,400,!0),t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${s}`)},"draw"),f={draw:l},L={parser:g,db:c,renderer:f};export{L as diagram};
|
||||
139
lightrag/api/webui/assets/journeyDiagram-EWQZEKCU-CKigKGVk.js
generated
Normal file
139
lightrag/api/webui/assets/journeyDiagram-EWQZEKCU-CKigKGVk.js
generated
Normal file
File diff suppressed because one or more lines are too long
89
lightrag/api/webui/assets/kanban-definition-ZSS6B67P-CRpdp8Fz.js
generated
Normal file
89
lightrag/api/webui/assets/kanban-definition-ZSS6B67P-CRpdp8Fz.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/layout-C99uYcpp.js
generated
Normal file
1
lightrag/api/webui/assets/layout-C99uYcpp.js
generated
Normal file
File diff suppressed because one or more lines are too long
48
lightrag/api/webui/assets/markdown-vendor-C1oKx5V8.js
generated
Normal file
48
lightrag/api/webui/assets/markdown-vendor-C1oKx5V8.js
generated
Normal file
File diff suppressed because one or more lines are too long
217
lightrag/api/webui/assets/mermaid-vendor-CpW20EHd.js
generated
Normal file
217
lightrag/api/webui/assets/mermaid-vendor-CpW20EHd.js
generated
Normal file
File diff suppressed because one or more lines are too long
95
lightrag/api/webui/assets/mindmap-definition-6CBA2TL7-B8jKN3AE.js
generated
Normal file
95
lightrag/api/webui/assets/mindmap-definition-6CBA2TL7-B8jKN3AE.js
generated
Normal file
File diff suppressed because one or more lines are too long
30
lightrag/api/webui/assets/pieDiagram-NIOCPIFQ-DqOw1dnr.js
generated
Normal file
30
lightrag/api/webui/assets/pieDiagram-NIOCPIFQ-DqOw1dnr.js
generated
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import{p as N}from"./chunk-353BL4L5-0V1KVYyT.js";import{_ as i,g as B,s as U,a as q,b as H,t as K,q as V,l as C,c as Z,F as j,K as J,M as Q,N as z,O as X,e as Y,z as tt,P as et,H as at}from"./mermaid-vendor-CpW20EHd.js";import{p as rt}from"./treemap-75Q7IDZK-CSah7hvo.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";import"./_baseUniq-BZ_JDEKn.js";import"./_basePickBy-C1BlOoDW.js";import"./clone-CDvVvGlj.js";var it=at.pie,D={sections:new Map,showData:!1},f=D.sections,w=D.showData,st=structuredClone(it),ot=i(()=>structuredClone(st),"getConfig"),nt=i(()=>{f=new Map,w=D.showData,tt()},"clear"),lt=i(({label:t,value:a})=>{f.has(t)||(f.set(t,a),C.debug(`added new section: ${t}, with value: ${a}`))},"addSection"),ct=i(()=>f,"getSections"),pt=i(t=>{w=t},"setShowData"),dt=i(()=>w,"getShowData"),F={getConfig:ot,clear:nt,setDiagramTitle:V,getDiagramTitle:K,setAccTitle:H,getAccTitle:q,setAccDescription:U,getAccDescription:B,addSection:lt,getSections:ct,setShowData:pt,getShowData:dt},gt=i((t,a)=>{N(t,a),a.setShowData(t.showData),t.sections.map(a.addSection)},"populateDb"),ut={parse:i(async t=>{const a=await rt("pie",t);C.debug(a),gt(a,F)},"parse")},mt=i(t=>`
|
||||
.pieCircle{
|
||||
stroke: ${t.pieStrokeColor};
|
||||
stroke-width : ${t.pieStrokeWidth};
|
||||
opacity : ${t.pieOpacity};
|
||||
}
|
||||
.pieOuterCircle{
|
||||
stroke: ${t.pieOuterStrokeColor};
|
||||
stroke-width: ${t.pieOuterStrokeWidth};
|
||||
fill: none;
|
||||
}
|
||||
.pieTitleText {
|
||||
text-anchor: middle;
|
||||
font-size: ${t.pieTitleTextSize};
|
||||
fill: ${t.pieTitleTextColor};
|
||||
font-family: ${t.fontFamily};
|
||||
}
|
||||
.slice {
|
||||
font-family: ${t.fontFamily};
|
||||
fill: ${t.pieSectionTextColor};
|
||||
font-size:${t.pieSectionTextSize};
|
||||
// fill: white;
|
||||
}
|
||||
.legend text {
|
||||
fill: ${t.pieLegendTextColor};
|
||||
font-family: ${t.fontFamily};
|
||||
font-size: ${t.pieLegendTextSize};
|
||||
}
|
||||
`,"getStyles"),ft=mt,ht=i(t=>{const a=[...t.entries()].map(s=>({label:s[0],value:s[1]})).sort((s,n)=>n.value-s.value);return et().value(s=>s.value)(a)},"createPieArcs"),St=i((t,a,G,s)=>{C.debug(`rendering pie chart
|
||||
`+t);const n=s.db,y=Z(),T=j(n.getConfig(),y.pie),$=40,o=18,d=4,c=450,h=c,S=J(a),l=S.append("g");l.attr("transform","translate("+h/2+","+c/2+")");const{themeVariables:r}=y;let[A]=Q(r.pieOuterStrokeWidth);A??(A=2);const _=T.textPosition,g=Math.min(h,c)/2-$,M=z().innerRadius(0).outerRadius(g),O=z().innerRadius(g*_).outerRadius(g*_);l.append("circle").attr("cx",0).attr("cy",0).attr("r",g+A/2).attr("class","pieOuterCircle");const b=n.getSections(),v=ht(b),P=[r.pie1,r.pie2,r.pie3,r.pie4,r.pie5,r.pie6,r.pie7,r.pie8,r.pie9,r.pie10,r.pie11,r.pie12],p=X(P);l.selectAll("mySlices").data(v).enter().append("path").attr("d",M).attr("fill",e=>p(e.data.label)).attr("class","pieCircle");let E=0;b.forEach(e=>{E+=e}),l.selectAll("mySlices").data(v).enter().append("text").text(e=>(e.data.value/E*100).toFixed(0)+"%").attr("transform",e=>"translate("+O.centroid(e)+")").style("text-anchor","middle").attr("class","slice"),l.append("text").text(n.getDiagramTitle()).attr("x",0).attr("y",-400/2).attr("class","pieTitleText");const x=l.selectAll(".legend").data(p.domain()).enter().append("g").attr("class","legend").attr("transform",(e,u)=>{const m=o+d,R=m*p.domain().length/2,I=12*o,L=u*m-R;return"translate("+I+","+L+")"});x.append("rect").attr("width",o).attr("height",o).style("fill",p).style("stroke",p),x.data(v).append("text").attr("x",o+d).attr("y",o-d).text(e=>{const{label:u,value:m}=e.data;return n.getShowData()?`${u} [${m}]`:u});const W=Math.max(...x.selectAll("text").nodes().map(e=>(e==null?void 0:e.getBoundingClientRect().width)??0)),k=h+$+o+d+W;S.attr("viewBox",`0 0 ${k} ${c}`),Y(S,c,k,T.useMaxWidth)},"draw"),vt={draw:St},kt={parser:ut,db:F,renderer:vt,styles:ft};export{kt as diagram};
|
||||
7
lightrag/api/webui/assets/quadrantDiagram-2OG54O6I-5iYk2CUx.js
generated
Normal file
7
lightrag/api/webui/assets/quadrantDiagram-2OG54O6I-5iYk2CUx.js
generated
Normal file
File diff suppressed because one or more lines are too long
64
lightrag/api/webui/assets/requirementDiagram-QOLK2EJ7-kz77VW2q.js
generated
Normal file
64
lightrag/api/webui/assets/requirementDiagram-QOLK2EJ7-kz77VW2q.js
generated
Normal file
File diff suppressed because one or more lines are too long
10
lightrag/api/webui/assets/sankeyDiagram-4UZDY2LN-CwCtr7m3.js
generated
Normal file
10
lightrag/api/webui/assets/sankeyDiagram-4UZDY2LN-CwCtr7m3.js
generated
Normal file
File diff suppressed because one or more lines are too long
122
lightrag/api/webui/assets/sequenceDiagram-SKLFT4DO-DL_1Zl67.js
generated
Normal file
122
lightrag/api/webui/assets/sequenceDiagram-SKLFT4DO-DL_1Zl67.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/stateDiagram-MI5ZYTHO-t3bgLK2B.js
generated
Normal file
1
lightrag/api/webui/assets/stateDiagram-MI5ZYTHO-t3bgLK2B.js
generated
Normal file
File diff suppressed because one or more lines are too long
1
lightrag/api/webui/assets/stateDiagram-v2-5AN5P6BG-BvKEglP2.js
generated
Normal file
1
lightrag/api/webui/assets/stateDiagram-v2-5AN5P6BG-BvKEglP2.js
generated
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{s as r,b as e,a,S as i}from"./chunk-OW32GOEJ-DF1Nd_2F.js";import{_ as s}from"./mermaid-vendor-CpW20EHd.js";import"./chunk-BFAMUDN2-CHovHiOg.js";import"./chunk-SKB7J2MH-CqH3ZkpA.js";import"./feature-graph-xUsMo1iK.js";import"./react-vendor-DEwriMA6.js";import"./graph-vendor-B-X5JegA.js";import"./ui-vendor-CeCm8EER.js";import"./utils-vendor-BysuhMZA.js";var f={parser:a,get db(){return new i(2)},renderer:e,styles:r,init:s(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{f as diagram};
|
||||
61
lightrag/api/webui/assets/timeline-definition-MYPXXCX6-BctbYu9h.js
generated
Normal file
61
lightrag/api/webui/assets/timeline-definition-MYPXXCX6-BctbYu9h.js
generated
Normal file
File diff suppressed because one or more lines are too long
128
lightrag/api/webui/assets/treemap-75Q7IDZK-CSah7hvo.js
generated
Normal file
128
lightrag/api/webui/assets/treemap-75Q7IDZK-CSah7hvo.js
generated
Normal file
File diff suppressed because one or more lines are too long
7
lightrag/api/webui/assets/xychartDiagram-H2YORKM3-Cx_Nblst.js
generated
Normal file
7
lightrag/api/webui/assets/xychartDiagram-H2YORKM3-Cx_Nblst.js
generated
Normal file
File diff suppressed because one or more lines are too long
15
lightrag/api/webui/index.html
generated
15
lightrag/api/webui/index.html
generated
|
|
@ -8,20 +8,21 @@
|
|||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Lightrag</title>
|
||||
<script type="module" crossorigin src="/webui/assets/index-Bjp1KQxU.js"></script>
|
||||
<script type="module" crossorigin src="/webui/assets/index-oYZPo1xP.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/react-vendor-DEwriMA6.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/ui-vendor-CeCm8EER.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/graph-vendor-B-X5JegA.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/utils-vendor-BysuhMZA.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-graph-bahMe5Gt.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-documents-cF4-Vta6.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/mermaid-vendor-BOzHoVUU.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/markdown-vendor-DmIvJdn7.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-retrieval-CnKoygV2.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-graph-xUsMo1iK.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-documents-22OwnQq9.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/mermaid-vendor-CpW20EHd.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/markdown-vendor-C1oKx5V8.js">
|
||||
<link rel="modulepreload" crossorigin href="/webui/assets/feature-retrieval-CeceOXFg.js">
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/feature-graph-BipNuM18.css">
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-BvrNHAMA.css">
|
||||
<link rel="stylesheet" crossorigin href="/webui/assets/index-CEupRNOQ.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -675,6 +675,8 @@ class BaseGraphStorage(StorageNameSpace, ABC):
|
|||
class DocStatus(str, Enum):
|
||||
"""Document processing status"""
|
||||
|
||||
READY = "ready"
|
||||
HANDLING = "handling"
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
PROCESSED = "processed"
|
||||
|
|
@ -707,6 +709,12 @@ class DocProcessingStatus:
|
|||
"""Error message if failed"""
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
"""Additional metadata"""
|
||||
multimodal_content: list[dict[str, Any]] | None = None
|
||||
"""raganything: multimodal_content"""
|
||||
multimodal_processed: bool | None = None
|
||||
"""raganything: multimodal_processed"""
|
||||
scheme_name: str | None = None
|
||||
"""lightrag or raganything"""
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import warnings
|
|||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from functools import partial
|
||||
from pathlib import Path
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncIterator,
|
||||
|
|
@ -98,6 +99,7 @@ from .utils import (
|
|||
)
|
||||
from .types import KnowledgeGraph
|
||||
from dotenv import load_dotenv
|
||||
from .ragmanager import RAGManager
|
||||
|
||||
# use the .env that is inside the current folder
|
||||
# allows to use different .env file for each lightrag instance
|
||||
|
|
@ -135,6 +137,9 @@ class LightRAG:
|
|||
doc_status_storage: str = field(default="JsonDocStatusStorage")
|
||||
"""Storage type for tracking document processing statuses."""
|
||||
|
||||
input_dir: str = field(default_factory=lambda: os.getenv("INPUT_DIR", "./inputs"))
|
||||
"""Directory containing input documents"""
|
||||
|
||||
# Workspace
|
||||
# ---
|
||||
|
||||
|
|
@ -863,16 +868,22 @@ class LightRAG:
|
|||
def insert(
|
||||
self,
|
||||
input: str | list[str],
|
||||
multimodal_content: list[dict[str, Any]]
|
||||
| list[list[dict[str, Any]]]
|
||||
| None = None,
|
||||
split_by_character: str | None = None,
|
||||
split_by_character_only: bool = False,
|
||||
ids: str | list[str] | None = None,
|
||||
file_paths: str | list[str] | None = None,
|
||||
track_id: str | None = None,
|
||||
scheme_name: str | None = None,
|
||||
) -> str:
|
||||
"""Sync Insert documents with checkpoint support
|
||||
|
||||
Args:
|
||||
input: Single document string or list of document strings
|
||||
multimodal_content (list[dict[str, Any]] | list[list[dict[str, Any]]] | None, optional):
|
||||
Multimodal content (images, tables, equations) associated with documents
|
||||
split_by_character: if split_by_character is not None, split the string by character, if chunk longer than
|
||||
chunk_token_size, it will be split again by token size.
|
||||
split_by_character_only: if split_by_character_only is True, split the string by character only, when
|
||||
|
|
@ -880,6 +891,7 @@ class LightRAG:
|
|||
ids: single string of the document ID or list of unique document IDs, if not provided, MD5 hash IDs will be generated
|
||||
file_paths: single string of the file path or list of file paths, used for citation
|
||||
track_id: tracking ID for monitoring processing status, if not provided, will be generated
|
||||
scheme_name (str | None, optional): Scheme name for categorizing documents
|
||||
|
||||
Returns:
|
||||
str: tracking ID for monitoring processing status
|
||||
|
|
@ -888,27 +900,35 @@ class LightRAG:
|
|||
return loop.run_until_complete(
|
||||
self.ainsert(
|
||||
input,
|
||||
multimodal_content,
|
||||
split_by_character,
|
||||
split_by_character_only,
|
||||
ids,
|
||||
file_paths,
|
||||
track_id,
|
||||
scheme_name,
|
||||
)
|
||||
)
|
||||
|
||||
async def ainsert(
|
||||
self,
|
||||
input: str | list[str],
|
||||
multimodal_content: list[dict[str, Any]]
|
||||
| list[list[dict[str, Any]]]
|
||||
| None = None,
|
||||
split_by_character: str | None = None,
|
||||
split_by_character_only: bool = False,
|
||||
ids: str | list[str] | None = None,
|
||||
file_paths: str | list[str] | None = None,
|
||||
track_id: str | None = None,
|
||||
scheme_name: str | None = None,
|
||||
) -> str:
|
||||
"""Async Insert documents with checkpoint support
|
||||
|
||||
Args:
|
||||
input: Single document string or list of document strings
|
||||
multimodal_content (list[dict[str, Any]] | list[list[dict[str, Any]]] | None, optional):
|
||||
Multimodal content (images, tables, equations) associated with documents
|
||||
split_by_character: if split_by_character is not None, split the string by character, if chunk longer than
|
||||
chunk_token_size, it will be split again by token size.
|
||||
split_by_character_only: if split_by_character_only is True, split the string by character only, when
|
||||
|
|
@ -916,6 +936,7 @@ class LightRAG:
|
|||
ids: list of unique document IDs, if not provided, MD5 hash IDs will be generated
|
||||
file_paths: list of file paths corresponding to each document, used for citation
|
||||
track_id: tracking ID for monitoring processing status, if not provided, will be generated
|
||||
scheme_name (str | None, optional): Scheme name for categorizing documents
|
||||
|
||||
Returns:
|
||||
str: tracking ID for monitoring processing status
|
||||
|
|
@ -924,13 +945,83 @@ class LightRAG:
|
|||
if track_id is None:
|
||||
track_id = generate_track_id("insert")
|
||||
|
||||
await self.apipeline_enqueue_documents(input, ids, file_paths, track_id)
|
||||
paths_to_check = [file_paths] if isinstance(file_paths, str) else file_paths
|
||||
base_input_dir = Path(self.input_dir)
|
||||
if self.workspace:
|
||||
current_input_dir = base_input_dir / self.workspace
|
||||
else:
|
||||
current_input_dir = base_input_dir
|
||||
|
||||
await self.apipeline_enqueue_documents(
|
||||
input,
|
||||
multimodal_content,
|
||||
ids,
|
||||
file_paths,
|
||||
track_id,
|
||||
scheme_name=scheme_name,
|
||||
)
|
||||
|
||||
for file_path in paths_to_check:
|
||||
current_file_path = current_input_dir / file_path
|
||||
if current_file_path.exists():
|
||||
self.move_file_to_enqueue(current_file_path)
|
||||
else:
|
||||
continue
|
||||
|
||||
await self.apipeline_process_enqueue_documents(
|
||||
split_by_character, split_by_character_only
|
||||
)
|
||||
|
||||
return track_id
|
||||
|
||||
def move_file_to_enqueue(self, file_path):
|
||||
try:
|
||||
enqueued_dir = file_path.parent / "__enqueued__"
|
||||
enqueued_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Generate unique filename to avoid conflicts
|
||||
unique_filename = self.get_unique_filename_in_enqueued(
|
||||
enqueued_dir, file_path.name
|
||||
)
|
||||
target_path = enqueued_dir / unique_filename
|
||||
|
||||
# Move the file
|
||||
file_path.rename(target_path)
|
||||
logger.debug(
|
||||
f"Moved file to enqueued directory: {file_path.name} -> {unique_filename}"
|
||||
)
|
||||
|
||||
except Exception as move_error:
|
||||
logger.error(
|
||||
f"Failed to move file {file_path.name} to __enqueued__ directory: {move_error}"
|
||||
)
|
||||
# Don't affect the main function's success status
|
||||
|
||||
def get_unique_filename_in_enqueued(
|
||||
self, target_dir: Path, original_name: str
|
||||
) -> str:
|
||||
from pathlib import Path
|
||||
import time
|
||||
|
||||
original_path = Path(original_name)
|
||||
base_name = original_path.stem
|
||||
extension = original_path.suffix
|
||||
|
||||
# Try original name first
|
||||
if not (target_dir / original_name).exists():
|
||||
return original_name
|
||||
|
||||
# Try with numeric suffixes 001-999
|
||||
for i in range(1, 1000):
|
||||
suffix = f"{i:03d}"
|
||||
new_name = f"{base_name}_{suffix}{extension}"
|
||||
if not (target_dir / new_name).exists():
|
||||
return new_name
|
||||
|
||||
# Fallback with timestamp if all 999 slots are taken
|
||||
timestamp = int(time.time())
|
||||
return f"{base_name}_{timestamp}{extension}"
|
||||
|
||||
# TODO: deprecated, use insert instead
|
||||
def insert_custom_chunks(
|
||||
self,
|
||||
|
|
@ -1006,9 +1097,13 @@ class LightRAG:
|
|||
async def apipeline_enqueue_documents(
|
||||
self,
|
||||
input: str | list[str],
|
||||
multimodal_content: list[dict[str, Any]]
|
||||
| list[list[dict[str, Any]]]
|
||||
| None = None,
|
||||
ids: list[str] | None = None,
|
||||
file_paths: str | list[str] | None = None,
|
||||
track_id: str | None = None,
|
||||
scheme_name: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Pipeline for Processing Documents
|
||||
|
|
@ -1020,9 +1115,12 @@ class LightRAG:
|
|||
|
||||
Args:
|
||||
input: Single document string or list of document strings
|
||||
multimodal_content (list[dict[str, Any]] | list[list[dict[str, Any]]] | None, optional):
|
||||
Multimodal content (images, tables, equations) associated with documents
|
||||
ids: list of unique document IDs, if not provided, MD5 hash IDs will be generated
|
||||
file_paths: list of file paths corresponding to each document, used for citation
|
||||
track_id: tracking ID for monitoring processing status, if not provided, will be generated with "enqueue" prefix
|
||||
scheme_name (str | None, optional): Scheme name for categorizing documents
|
||||
|
||||
Returns:
|
||||
str: tracking ID for monitoring processing status
|
||||
|
|
@ -1093,6 +1191,7 @@ class LightRAG:
|
|||
id_: {
|
||||
"status": DocStatus.PENDING,
|
||||
"content_summary": get_content_summary(content_data["content"]),
|
||||
"multimodal_content": multimodal_content,
|
||||
"content_length": len(content_data["content"]),
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
|
|
@ -1100,6 +1199,7 @@ class LightRAG:
|
|||
"file_path"
|
||||
], # Store file path in document status
|
||||
"track_id": track_id, # Store track_id in document status
|
||||
"scheme_name": scheme_name,
|
||||
}
|
||||
for id_, content_data in contents.items()
|
||||
}
|
||||
|
|
@ -1130,6 +1230,12 @@ class LightRAG:
|
|||
if doc_id in new_docs
|
||||
}
|
||||
|
||||
new_docs_idList = [
|
||||
f"doc-pre-{new_docs[doc_id]['file_path']}"
|
||||
for doc_id in unique_new_doc_ids
|
||||
if doc_id in new_docs
|
||||
]
|
||||
|
||||
if not new_docs:
|
||||
logger.warning("No new unique documents were found.")
|
||||
return
|
||||
|
|
@ -1147,6 +1253,10 @@ class LightRAG:
|
|||
# Store document status (without content)
|
||||
await self.doc_status.upsert(new_docs)
|
||||
logger.debug(f"Stored {len(new_docs)} new unique documents")
|
||||
await self.doc_status.index_done_callback()
|
||||
|
||||
await self.doc_status.delete(new_docs_idList)
|
||||
logger.info(f"Deleted {new_docs_idList} Successful")
|
||||
|
||||
return track_id
|
||||
|
||||
|
|
@ -1322,6 +1432,7 @@ class LightRAG:
|
|||
docs_to_reset[doc_id] = {
|
||||
"status": DocStatus.PENDING,
|
||||
"content_summary": status_doc.content_summary,
|
||||
"multimodal_content": status_doc.multimodal_content,
|
||||
"content_length": status_doc.content_length,
|
||||
"created_at": status_doc.created_at,
|
||||
"updated_at": datetime.now(timezone.utc).isoformat(),
|
||||
|
|
@ -1330,11 +1441,15 @@ class LightRAG:
|
|||
# Clear any error messages and processing metadata
|
||||
"error_msg": "",
|
||||
"metadata": {},
|
||||
"scheme_name": status_doc.scheme_name,
|
||||
}
|
||||
|
||||
# Update the status in to_process_docs as well
|
||||
status_doc.status = DocStatus.PENDING
|
||||
reset_count += 1
|
||||
logger.info(
|
||||
f"Document {status_doc.file_path} from PROCESSING/FAILED to PENDING status"
|
||||
)
|
||||
|
||||
# Update doc_status storage if there are documents to reset
|
||||
if docs_to_reset:
|
||||
|
|
@ -1557,6 +1672,7 @@ class LightRAG:
|
|||
chunks.keys()
|
||||
), # Save chunks list
|
||||
"content_summary": status_doc.content_summary,
|
||||
"multimodal_content": status_doc.multimodal_content,
|
||||
"content_length": status_doc.content_length,
|
||||
"created_at": status_doc.created_at,
|
||||
"updated_at": datetime.now(
|
||||
|
|
@ -1567,6 +1683,7 @@ class LightRAG:
|
|||
"metadata": {
|
||||
"processing_start_time": processing_start_time
|
||||
},
|
||||
"scheme_name": status_doc.scheme_name,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1632,6 +1749,7 @@ class LightRAG:
|
|||
"status": DocStatus.FAILED,
|
||||
"error_msg": str(e),
|
||||
"content_summary": status_doc.content_summary,
|
||||
"multimodal_content": status_doc.multimodal_content,
|
||||
"content_length": status_doc.content_length,
|
||||
"created_at": status_doc.created_at,
|
||||
"updated_at": datetime.now(
|
||||
|
|
@ -1643,6 +1761,7 @@ class LightRAG:
|
|||
"processing_start_time": processing_start_time,
|
||||
"processing_end_time": processing_end_time,
|
||||
},
|
||||
"scheme_name": status_doc.scheme_name,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1675,10 +1794,11 @@ class LightRAG:
|
|||
await self.doc_status.upsert(
|
||||
{
|
||||
doc_id: {
|
||||
"status": DocStatus.PROCESSED,
|
||||
"status": DocStatus.PROCESSING,
|
||||
"chunks_count": len(chunks),
|
||||
"chunks_list": list(chunks.keys()),
|
||||
"content_summary": status_doc.content_summary,
|
||||
"multimodal_content": status_doc.multimodal_content,
|
||||
"content_length": status_doc.content_length,
|
||||
"created_at": status_doc.created_at,
|
||||
"updated_at": datetime.now(
|
||||
|
|
@ -1690,6 +1810,32 @@ class LightRAG:
|
|||
"processing_start_time": processing_start_time,
|
||||
"processing_end_time": processing_end_time,
|
||||
},
|
||||
"scheme_name": status_doc.scheme_name,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
status_doc.multimodal_content
|
||||
and len(status_doc.multimodal_content) > 0
|
||||
):
|
||||
raganything_instance = RAGManager.get_rag()
|
||||
await raganything_instance._process_multimodal_content(
|
||||
status_doc.multimodal_content,
|
||||
status_doc.file_path,
|
||||
doc_id,
|
||||
pipeline_status=pipeline_status,
|
||||
pipeline_status_lock=pipeline_status_lock,
|
||||
)
|
||||
|
||||
current_doc_status = await self.doc_status.get_by_id(
|
||||
doc_id
|
||||
)
|
||||
await self.doc_status.upsert(
|
||||
{
|
||||
doc_id: {
|
||||
**current_doc_status,
|
||||
"status": DocStatus.PROCESSED,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -1733,6 +1879,7 @@ class LightRAG:
|
|||
"status": DocStatus.FAILED,
|
||||
"error_msg": str(e),
|
||||
"content_summary": status_doc.content_summary,
|
||||
"multimodal_content": status_doc.multimodal_content,
|
||||
"content_length": status_doc.content_length,
|
||||
"created_at": status_doc.created_at,
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
|
|
@ -1742,6 +1889,7 @@ class LightRAG:
|
|||
"processing_start_time": processing_start_time,
|
||||
"processing_end_time": processing_end_time,
|
||||
},
|
||||
"scheme_name": status_doc.scheme_name,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -2294,6 +2442,156 @@ class LightRAG:
|
|||
# Return the dictionary containing statuses only for the found document IDs
|
||||
return found_statuses
|
||||
|
||||
async def aclean_parse_cache_by_doc_ids(
|
||||
self, doc_ids: str | list[str]
|
||||
) -> dict[str, Any]:
|
||||
"""Asynchronously clean parse_cache entries for specified document IDs
|
||||
|
||||
Args:
|
||||
doc_ids: Single document ID string or list of document IDs
|
||||
|
||||
Returns:
|
||||
Dictionary containing cleanup results:
|
||||
- deleted_entries: List of deleted cache entries
|
||||
- not_found: List of document IDs not found
|
||||
- error: Error message (if operation fails)
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Normalize input to list
|
||||
if isinstance(doc_ids, str):
|
||||
doc_ids = [doc_ids]
|
||||
|
||||
result = {"deleted_entries": [], "not_found": [], "error": None}
|
||||
|
||||
try:
|
||||
# Build parse_cache file path using class storage location variables
|
||||
if self.workspace:
|
||||
# If workspace exists, use workspace subdirectory
|
||||
cache_file_path = (
|
||||
Path(self.working_dir)
|
||||
/ self.workspace
|
||||
/ "kv_store_parse_cache.json"
|
||||
)
|
||||
else:
|
||||
# Default to using working_dir
|
||||
cache_file_path = Path(self.working_dir) / "kv_store_parse_cache.json"
|
||||
|
||||
# Check if parse_cache file exists
|
||||
if not cache_file_path.exists():
|
||||
logger.warning(f"Parse cache file not found: {cache_file_path}")
|
||||
result["not_found"] = doc_ids.copy()
|
||||
return result
|
||||
|
||||
# Read current parse_cache data
|
||||
with open(cache_file_path, "r", encoding="utf-8") as f:
|
||||
cache_data = json.load(f)
|
||||
|
||||
# Find entries to delete and record found doc_ids
|
||||
entries_to_delete = []
|
||||
doc_ids_set = set(doc_ids)
|
||||
found_doc_ids = set()
|
||||
|
||||
for cache_key, cache_entry in cache_data.items():
|
||||
if (
|
||||
isinstance(cache_entry, dict)
|
||||
and cache_entry.get("doc_id") in doc_ids_set
|
||||
):
|
||||
entries_to_delete.append(cache_key)
|
||||
result["deleted_entries"].append(cache_key)
|
||||
found_doc_ids.add(cache_entry.get("doc_id"))
|
||||
|
||||
# Delete found entries
|
||||
for cache_key in entries_to_delete:
|
||||
del cache_data[cache_key]
|
||||
|
||||
# Find doc_ids not found
|
||||
result["not_found"] = list(doc_ids_set - found_doc_ids)
|
||||
|
||||
# Write back updated cache data
|
||||
with open(cache_file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(cache_data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
logger.info(
|
||||
f"Deleted {len(entries_to_delete)} parse_cache entries, document IDs: {doc_ids}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error cleaning parse_cache: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
|
||||
return result
|
||||
|
||||
def clean_parse_cache_by_doc_ids(self, doc_ids: str | list[str]) -> dict[str, Any]:
|
||||
"""Synchronously clean parse_cache entries for specified document IDs
|
||||
|
||||
Args:
|
||||
doc_ids: Single document ID string or list of document IDs
|
||||
|
||||
Returns:
|
||||
Dictionary containing cleanup results
|
||||
"""
|
||||
loop = always_get_an_event_loop()
|
||||
return loop.run_until_complete(self.aclean_parse_cache_by_doc_ids(doc_ids))
|
||||
|
||||
async def aclean_all_parse_cache(self) -> dict[str, Any]:
|
||||
"""Asynchronously clean all parse_cache entries
|
||||
|
||||
Returns:
|
||||
Dictionary containing cleanup results:
|
||||
- deleted_count: Number of deleted entries
|
||||
- error: Error message (if operation fails)
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
result = {"deleted_count": 0, "error": None}
|
||||
|
||||
try:
|
||||
# Build parse_cache file path
|
||||
if self.workspace:
|
||||
cache_file_path = (
|
||||
Path(self.working_dir)
|
||||
/ self.workspace
|
||||
/ "kv_store_parse_cache.json"
|
||||
)
|
||||
else:
|
||||
cache_file_path = Path(self.working_dir) / "kv_store_parse_cache.json"
|
||||
|
||||
if not cache_file_path.exists():
|
||||
logger.warning(f"Parse cache file not found: {cache_file_path}")
|
||||
return result
|
||||
|
||||
# Read current cache to count entries
|
||||
with open(cache_file_path, "r", encoding="utf-8") as f:
|
||||
cache_data = json.load(f)
|
||||
|
||||
result["deleted_count"] = len(cache_data)
|
||||
|
||||
# Clear all entries
|
||||
with open(cache_file_path, "w", encoding="utf-8") as f:
|
||||
json.dump({}, f, indent=2)
|
||||
|
||||
logger.info(f"Cleared all {result['deleted_count']} parse_cache entries")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error clearing parse_cache: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
result["error"] = error_msg
|
||||
|
||||
return result
|
||||
|
||||
def clean_all_parse_cache(self) -> dict[str, Any]:
|
||||
"""Synchronously clean all parse_cache entries
|
||||
|
||||
Returns:
|
||||
Dictionary containing cleanup results
|
||||
"""
|
||||
loop = always_get_an_event_loop()
|
||||
return loop.run_until_complete(self.aclean_all_parse_cache())
|
||||
|
||||
async def adelete_by_doc_id(self, doc_id: str) -> DeletionResult:
|
||||
"""Delete a document and all its related data, including chunks, graph elements, and cached entries.
|
||||
|
||||
|
|
|
|||
|
|
@ -1936,7 +1936,29 @@ async def merge_nodes_and_edges(
|
|||
if full_entities_storage and full_relations_storage and doc_id:
|
||||
try:
|
||||
# Merge all entities: original entities + entities added during edge processing
|
||||
final_entity_names = set()
|
||||
existing_entites_data = None
|
||||
existing_relations_data = None
|
||||
|
||||
try:
|
||||
existing_entites_data = await full_entities_storage.get_by_id(doc_id)
|
||||
existing_relations_data = await full_relations_storage.get_by_id(doc_id)
|
||||
except Exception as e:
|
||||
logger.debug(
|
||||
f"Could not retrieve existing entity/relation data for {doc_id}: {e}"
|
||||
)
|
||||
|
||||
existing_entites_names = set()
|
||||
if existing_entites_data and existing_entites_data.get("entity_names"):
|
||||
existing_entites_names.update(existing_entites_data["entity_names"])
|
||||
|
||||
existing_relation_pairs = set()
|
||||
if existing_relations_data and existing_relations_data.get(
|
||||
"relation_pairs"
|
||||
):
|
||||
for pair in existing_relations_data["relation_pairs"]:
|
||||
existing_relation_pairs.add(tuple(sorted(pair)))
|
||||
|
||||
final_entity_names = existing_entites_names.copy()
|
||||
|
||||
# Add original processed entities
|
||||
for entity_data in processed_entities:
|
||||
|
|
@ -1949,7 +1971,7 @@ async def merge_nodes_and_edges(
|
|||
final_entity_names.add(added_entity["entity_name"])
|
||||
|
||||
# Collect all relation pairs
|
||||
final_relation_pairs = set()
|
||||
final_relation_pairs = existing_relation_pairs.copy()
|
||||
for edge_data in processed_edges:
|
||||
if edge_data:
|
||||
src_id = edge_data.get("src_id")
|
||||
|
|
@ -1959,6 +1981,12 @@ async def merge_nodes_and_edges(
|
|||
final_relation_pairs.add(relation_pair)
|
||||
|
||||
log_message = f"Phase 3: Updating final {len(final_entity_names)}({len(processed_entities)}+{len(all_added_entities)}) entities and {len(final_relation_pairs)} relations from {doc_id}"
|
||||
new_entities_count = len(final_entity_names) - len(existing_entites_names)
|
||||
new_relation_count = len(final_relation_pairs) - len(
|
||||
existing_relation_pairs
|
||||
)
|
||||
|
||||
log_message = f"Phase 3: Merging storage - existing: {len(existing_entites_names)} entitites, {len(existing_relation_pairs)} relations; new: {new_entities_count} entities. {new_relation_count} relations; total: {len(final_entity_names)} entities, {len(final_relation_pairs)} relations"
|
||||
logger.info(log_message)
|
||||
async with pipeline_status_lock:
|
||||
pipeline_status["latest_message"] = log_message
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ PROMPTS["DEFAULT_COMPLETION_DELIMITER"] = "<|COMPLETE|>"
|
|||
|
||||
PROMPTS["DEFAULT_USER_PROMPT"] = "n/a"
|
||||
|
||||
|
||||
PROMPTS["entity_extraction_system_prompt"] = """---Role---
|
||||
You are a Knowledge Graph Specialist responsible for extracting entities and relationships from the input text.
|
||||
|
||||
|
||||
---Instructions---
|
||||
1. **Entity Extraction & Output:**
|
||||
* **Identification:** Identify clearly defined and meaningful entities in the input text.
|
||||
|
|
@ -58,6 +60,7 @@ You are a Knowledge Graph Specialist responsible for extracting entities and rel
|
|||
|
||||
8. **Completion Signal:** Output the literal string `{completion_delimiter}` only after all entities and relationships, following all criteria, have been completely extracted and outputted.
|
||||
|
||||
|
||||
---Examples---
|
||||
{examples}
|
||||
|
||||
|
|
|
|||
18
lightrag/ragmanager.py
Normal file
18
lightrag/ragmanager.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
class RAGManager:
|
||||
_instance = None
|
||||
_rag = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super().__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
def set_rag(cls, rag_instance):
|
||||
cls._rag = rag_instance
|
||||
|
||||
@classmethod
|
||||
def get_rag(cls):
|
||||
if cls._rag is None:
|
||||
raise ValueError("RAG instance not initialized!")
|
||||
return cls._rag
|
||||
|
|
@ -15,6 +15,7 @@ import GraphViewer from '@/features/GraphViewer'
|
|||
import DocumentManager from '@/features/DocumentManager'
|
||||
import RetrievalTesting from '@/features/RetrievalTesting'
|
||||
import ApiSite from '@/features/ApiSite'
|
||||
import { SchemeProvider } from '@/contexts/SchemeContext';
|
||||
|
||||
import { Tabs, TabsContent } from '@/components/ui/Tabs'
|
||||
|
||||
|
|
@ -204,9 +205,11 @@ function App() {
|
|||
>
|
||||
<SiteHeader />
|
||||
<div className="relative grow">
|
||||
<TabsContent value="documents" className="absolute top-0 right-0 bottom-0 left-0 overflow-auto">
|
||||
<DocumentManager />
|
||||
</TabsContent>
|
||||
<SchemeProvider>
|
||||
<TabsContent value="documents" className="absolute top-0 right-0 bottom-0 left-0 overflow-auto">
|
||||
<DocumentManager />
|
||||
</TabsContent>
|
||||
</SchemeProvider>
|
||||
<TabsContent value="knowledge-graph" className="absolute top-0 right-0 bottom-0 left-0 overflow-hidden">
|
||||
<GraphViewer />
|
||||
</TabsContent>
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export type DeleteDocResponse = {
|
|||
doc_id: string
|
||||
}
|
||||
|
||||
export type DocStatus = 'pending' | 'processing' | 'processed' | 'failed'
|
||||
export type DocStatus = 'pending' | 'processing' | 'processed' | 'ready' | 'handling' | 'failed'
|
||||
|
||||
export type DocStatusResponse = {
|
||||
id: string
|
||||
|
|
@ -175,6 +175,7 @@ export type DocStatusResponse = {
|
|||
error_msg?: string
|
||||
metadata?: Record<string, any>
|
||||
file_path: string
|
||||
scheme_name: string
|
||||
}
|
||||
|
||||
export type DocsStatusesResponse = {
|
||||
|
|
@ -252,6 +253,24 @@ export type LoginResponse = {
|
|||
webui_description?: string
|
||||
}
|
||||
|
||||
export type Scheme = {
|
||||
id: number;
|
||||
name: string;
|
||||
config: {
|
||||
framework: 'lightrag' | 'raganything';
|
||||
extractor?: 'mineru' | 'docling' | undefined; // Optional extractor field
|
||||
modelSource?: 'huggingface' | 'modelscope' | 'local' | undefined; // Optional model source field
|
||||
};
|
||||
};
|
||||
|
||||
type AddSchemeParams = Omit<Scheme, 'id'>;
|
||||
|
||||
export type SchemesResponse = {
|
||||
status: string;
|
||||
message: string;
|
||||
data: Scheme[];
|
||||
};
|
||||
|
||||
export const InvalidApiKeyError = 'Invalid API Key'
|
||||
export const RequireApiKeError = 'API Key required'
|
||||
|
||||
|
|
@ -305,6 +324,32 @@ axiosInstance.interceptors.response.use(
|
|||
)
|
||||
|
||||
// API methods
|
||||
export const getSchemes = async (): Promise<SchemesResponse> => {
|
||||
const response = await axiosInstance.get('/documents/schemes');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const saveSchemes = async (schemes: Scheme[]): Promise<{ message: string }> => {
|
||||
const response = await axiosInstance.post('/documents/schemes', schemes);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const addScheme = async (scheme: AddSchemeParams): Promise<Scheme> => {
|
||||
try {
|
||||
const response = await axiosInstance.post('/documents/schemes/add', scheme);
|
||||
// 验证响应数据是否符合 Scheme 类型(可选,取决于 axios 的配置)
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to add scheme:', error);
|
||||
throw error; // 重新抛出错误,由调用方处理
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteScheme = async (schemeId: number): Promise<{ message: string }> => {
|
||||
const response = await axiosInstance.delete(`/documents/schemes/${schemeId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const queryGraphs = async (
|
||||
label: string,
|
||||
maxDepth: number,
|
||||
|
|
@ -338,8 +383,10 @@ export const getDocuments = async (): Promise<DocsStatusesResponse> => {
|
|||
return response.data
|
||||
}
|
||||
|
||||
export const scanNewDocuments = async (): Promise<ScanResponse> => {
|
||||
const response = await axiosInstance.post('/documents/scan')
|
||||
export const scanNewDocuments = async (schemeConfig: any): Promise<ScanResponse> => {
|
||||
const response = await axiosInstance.post('/documents/scan', {
|
||||
schemeConfig
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
|
|
@ -550,10 +597,12 @@ export const insertTexts = async (texts: string[]): Promise<DocActionResponse> =
|
|||
|
||||
export const uploadDocument = async (
|
||||
file: File,
|
||||
schemeId: number | '',
|
||||
onUploadProgress?: (percentCompleted: number) => void
|
||||
): Promise<DocActionResponse> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
formData.append('schemeId', schemeId.toString())
|
||||
|
||||
const response = await axiosInstance.post('/documents/upload', formData, {
|
||||
headers: {
|
||||
|
|
@ -573,11 +622,12 @@ export const uploadDocument = async (
|
|||
|
||||
export const batchUploadDocuments = async (
|
||||
files: File[],
|
||||
schemeId: number | '',
|
||||
onUploadProgress?: (fileName: string, percentCompleted: number) => void
|
||||
): Promise<DocActionResponse[]> => {
|
||||
return await Promise.all(
|
||||
files.map(async (file) => {
|
||||
return await uploadDocument(file, (percentCompleted) => {
|
||||
return await uploadDocument(file, schemeId, (percentCompleted) => {
|
||||
onUploadProgress?.(file.name, percentCompleted)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
.scheme-manager-container {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.toggle-button {
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.toggle-button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.scheme-modal {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background-color: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.close-button {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.close-button:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
display: flex;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
flex: 0 0 300px;
|
||||
padding: 16px;
|
||||
border-right: 1px solid #ddd;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.add-scheme-form {
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.scheme-name-input {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
padding: 8px 12px;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.add-button:hover {
|
||||
background-color: #0b7dda;
|
||||
}
|
||||
|
||||
.scheme-list ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.scheme-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
margin-bottom: 8px;
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.scheme-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.scheme-item.active {
|
||||
background-color: #e3f2fd;
|
||||
border-left: 3px solid #2196F3;
|
||||
}
|
||||
|
||||
.delete-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #f44336;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.delete-button:hover {
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.empty-message, .select-message {
|
||||
color: #666;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.config-form {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
import React, { useRef,useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger
|
||||
} from '@/components/ui/Dialog';
|
||||
import Button from "@/components/ui/Button";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { Alert, AlertDescription } from "@/components/ui/Alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import {
|
||||
getSchemes,
|
||||
saveSchemes,
|
||||
addScheme,
|
||||
deleteScheme,
|
||||
Scheme
|
||||
} from '@/api/lightrag';
|
||||
import { useScheme } from '@/contexts/SchemeContext';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface SchemeConfig {
|
||||
framework: 'lightrag' | 'raganything';
|
||||
extractor?: 'mineru' | 'docling';
|
||||
modelSource?: 'huggingface' | 'modelscope' | 'local';
|
||||
}
|
||||
|
||||
const SchemeManagerDialog = () => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [schemes, setSchemes] = useState<Scheme[]>([]);
|
||||
const [newSchemeName, setNewSchemeName] = useState("");
|
||||
const [error, _setError] = useState<string | undefined>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const setError = (err?: string) => _setError(err);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const { selectedScheme, setSelectedScheme } = useScheme();
|
||||
|
||||
// 加载方案数据
|
||||
useEffect(() => {
|
||||
const loadSchemes = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await getSchemes();
|
||||
setSchemes(response.data);
|
||||
localStorage.getItem('selectedSchemeId') && setSelectedScheme(response.data.find(s => s.id === Number(localStorage.getItem('selectedSchemeId'))) || undefined);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t('schemeManager.errors.loadFailed'));
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
loadSchemes();
|
||||
}, []);
|
||||
|
||||
// 自动滚动到底部
|
||||
useEffect(() => {
|
||||
handleSelectScheme(selectedScheme?.id!);
|
||||
if (!scrollRef.current) return;
|
||||
const scrollToBottom = () => {
|
||||
const container = scrollRef.current!;
|
||||
const { scrollHeight } = container;
|
||||
container.scrollTop = scrollHeight;
|
||||
};
|
||||
setTimeout(scrollToBottom, 0);
|
||||
}, [schemes]);
|
||||
|
||||
// 检查方案名是否已存在
|
||||
const isNameTaken = (name: string): boolean => {
|
||||
return schemes.some(scheme => scheme.name.trim() === name.trim());
|
||||
};
|
||||
|
||||
// 选中方案(更新 Context)
|
||||
const handleSelectScheme = (schemeId: number) => {
|
||||
const scheme = schemes.find((s) => s.id === schemeId);
|
||||
if (scheme) {
|
||||
setSelectedScheme(scheme);
|
||||
localStorage.setItem('selectedSchemeId', String(scheme.id));
|
||||
}
|
||||
};
|
||||
|
||||
// 添加新方案
|
||||
const handleAddScheme = async () => {
|
||||
const trimmedName = newSchemeName.trim();
|
||||
if (!trimmedName) {
|
||||
setError(t('schemeManager.errors.nameEmpty'));
|
||||
return;
|
||||
}
|
||||
if (isNameTaken(trimmedName)) {
|
||||
setError(t('schemeManager.errors.nameExists'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newScheme = await addScheme({
|
||||
name: trimmedName,
|
||||
config: { framework: 'lightrag', extractor: undefined, modelSource: undefined },
|
||||
});
|
||||
|
||||
// 更新方案列表
|
||||
setSchemes((prevSchemes) => [...prevSchemes, newScheme]);
|
||||
|
||||
// 选中新方案
|
||||
setSelectedScheme(newScheme);
|
||||
|
||||
// 清空输入和错误
|
||||
setNewSchemeName("");
|
||||
setError(undefined);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t('schemeManager.errors.addFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 删除方案
|
||||
const handleDeleteScheme = async (schemeId: number) => {
|
||||
try {
|
||||
await deleteScheme(schemeId);
|
||||
setSchemes(schemes.filter(s => s.id !== schemeId));
|
||||
if (selectedScheme?.id === schemeId) {
|
||||
setSelectedScheme(undefined); // 清除 Context 中的选中状态
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : t('schemeManager.errors.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 更新方案配置
|
||||
const handleConfigChange = async (updates: Partial<SchemeConfig>) => {
|
||||
if (!selectedScheme) return;
|
||||
|
||||
const updatedScheme = {
|
||||
...selectedScheme,
|
||||
config: {
|
||||
...selectedScheme.config,
|
||||
...updates,
|
||||
framework: updates.framework ?? selectedScheme.config?.framework ?? 'lightrag',
|
||||
extractor: updates.extractor || selectedScheme.config?.extractor || (updates.framework === 'raganything' ? 'mineru' : undefined),
|
||||
modelSource: updates.modelSource || selectedScheme.config?.modelSource || (updates.extractor === 'mineru' ? 'huggingface' : undefined),
|
||||
},
|
||||
};
|
||||
|
||||
setSchemes(schemes.map(s => s.id === selectedScheme.id ? updatedScheme : s));
|
||||
await saveSchemes([updatedScheme]);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex justify-center items-center h-screen">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="default" side="bottom" size="sm">
|
||||
<PlusIcon className="size-4" />
|
||||
{t('schemeManager.button')}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="sm:max-w-[800px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('schemeManager.title')}</DialogTitle>
|
||||
<DialogDescription>{t('schemeManager.description')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex h-[500px] gap-4">
|
||||
{/* 左侧:方案列表 */}
|
||||
<div className="w-1/3 rounded-lg border p-4 bg-gray-50 flex flex-col dark:bg-zinc-800 dark:text-zinc-100">
|
||||
<h3 className="mb-4 font-semibold">{t('schemeManager.schemeList')}</h3>
|
||||
|
||||
{/* 创建新方案输入框 */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={newSchemeName}
|
||||
onChange={(e) => {
|
||||
if (e.target.value.length > 50) return;
|
||||
setNewSchemeName(e.target.value);
|
||||
setError(undefined);
|
||||
}}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleAddScheme()}
|
||||
placeholder={t('schemeManager.inputPlaceholder')}
|
||||
className="w-full px-3 py-1.5 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<Button onClick={handleAddScheme} size="sm">
|
||||
<PlusIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 错误提示 */}
|
||||
{error && (
|
||||
<Alert variant="destructive" className="mb-4">
|
||||
<AlertCircle className="size-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* 方案列表 */}
|
||||
<div ref={scrollRef} className="flex-1 overflow-y-auto border rounded-md p-1 dark:bg-zinc-800 dark:text-zinc-100">
|
||||
{schemes.length === 0 ? (
|
||||
<p className="text-gray-500 text-center py-4">{t('schemeManager.emptySchemes')}</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{schemes.map((scheme) => (
|
||||
<div
|
||||
key={scheme.id}
|
||||
className={`flex items-center justify-between p-2 rounded-md cursor-pointer transition-colors truncate ${
|
||||
selectedScheme?.id === scheme.id
|
||||
? "bg-blue-100 text-blue-700"
|
||||
: "hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => handleSelectScheme(scheme.id)}
|
||||
>
|
||||
<div className="flex-1 truncate mr-2" title={scheme.name}>
|
||||
{scheme.name}
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteScheme(scheme.id);
|
||||
}}
|
||||
className="ml-2 text-red-500 hover:text-red-700 hover:bg-red-100 rounded-full p-1 transition-colors"
|
||||
title={t('schemeManager.deleteTooltip')}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧:方案配置 */}
|
||||
<div className="flex-1 rounded-lg border p-4 bg-gray-50 dark:bg-zinc-800 dark:text-zinc-100">
|
||||
<h3 className="mb-4 font-semibold">{t('schemeManager.schemeConfig')}</h3>
|
||||
|
||||
{selectedScheme ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm mb-1">{t('schemeManager.processingFramework')}</label>
|
||||
<select
|
||||
value={selectedScheme.config?.framework || "lightrag"}
|
||||
onChange={(e) => handleConfigChange({ framework: e.target.value as 'lightrag' | 'raganything' })}
|
||||
className="w-full px-3 py-1.5 border rounded-md focus:outline-none dark:bg-zinc-800 dark:text-zinc-100"
|
||||
>
|
||||
<option value="lightrag">LightRAG</option>
|
||||
<option value="raganything">RAGAnything</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{selectedScheme.config?.framework === "raganything" && (
|
||||
<div>
|
||||
<label className="block text-sm mb-1">{t('schemeManager.extractionTool')}</label>
|
||||
<select
|
||||
value={selectedScheme.config?.extractor || "mineru"}
|
||||
onChange={(e) => handleConfigChange({ extractor: e.target.value as 'mineru' | 'docling' })}
|
||||
className="w-full px-3 py-1.5 border rounded-md focus:outline-none dark:bg-zinc-800 dark:text-zinc-100"
|
||||
>
|
||||
<option value="mineru">Mineru</option>
|
||||
<option value="docling">DocLing</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedScheme.config?.extractor === "mineru" && (
|
||||
<div>
|
||||
<label className="block text-sm mb-1">{t('schemeManager.modelSource')}</label>
|
||||
<select
|
||||
value={selectedScheme.config?.modelSource || "huggingface"}
|
||||
onChange={(e) => handleConfigChange({ modelSource: e.target.value as 'huggingface' | 'modelscope' | 'local' })}
|
||||
className="w-full px-3 py-1.5 border rounded-md focus:outline-none dark:bg-zinc-800 dark:text-zinc-100"
|
||||
>
|
||||
<option value="huggingface">huggingface</option>
|
||||
<option value="modelscope">modelscope</option>
|
||||
<option value="local">local</option>
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center h-[70%] text-gray-500">
|
||||
<AlertCircle className="size-12 mb-4 opacity-50" />
|
||||
<p>{t('schemeManager.selectSchemePrompt')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default SchemeManagerDialog;
|
||||
|
|
@ -16,6 +16,7 @@ import { uploadDocument } from '@/api/lightrag'
|
|||
|
||||
import { UploadIcon } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useScheme } from '@/contexts/SchemeContext';
|
||||
|
||||
interface UploadDocumentsDialogProps {
|
||||
onDocumentsUploaded?: () => Promise<void>
|
||||
|
|
@ -27,6 +28,7 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc
|
|||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [progresses, setProgresses] = useState<Record<string, number>>({})
|
||||
const [fileErrors, setFileErrors] = useState<Record<string, string>>({})
|
||||
const { selectedScheme } = useScheme();
|
||||
|
||||
const handleRejectedFiles = useCallback(
|
||||
(rejectedFiles: FileRejection[]) => {
|
||||
|
|
@ -58,6 +60,11 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc
|
|||
|
||||
const handleDocumentsUpload = useCallback(
|
||||
async (filesToUpload: File[]) => {
|
||||
if (!selectedScheme) {
|
||||
toast.error(t('schemeManager.upload.noSchemeSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUploading(true)
|
||||
let hasSuccessfulUpload = false
|
||||
|
||||
|
|
@ -95,7 +102,7 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc
|
|||
[file.name]: 0
|
||||
}))
|
||||
|
||||
const result = await uploadDocument(file, (percentCompleted: number) => {
|
||||
const result = await uploadDocument(file, selectedScheme?.id, (percentCompleted: number) => {
|
||||
console.debug(t('documentPanel.uploadDocuments.single.uploading', { name: file.name, percent: percentCompleted }))
|
||||
setProgresses((pre) => ({
|
||||
...pre,
|
||||
|
|
@ -175,7 +182,7 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc
|
|||
setIsUploading(false)
|
||||
}
|
||||
},
|
||||
[setIsUploading, setProgresses, setFileErrors, t, onDocumentsUploaded]
|
||||
[setIsUploading, setProgresses, setFileErrors, t, onDocumentsUploaded, selectedScheme]
|
||||
)
|
||||
|
||||
return (
|
||||
|
|
@ -201,7 +208,11 @@ export default function UploadDocumentsDialog({ onDocumentsUploaded }: UploadDoc
|
|||
<DialogHeader>
|
||||
<DialogTitle>{t('documentPanel.uploadDocuments.title')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{t('documentPanel.uploadDocuments.description')}
|
||||
{selectedScheme ? (
|
||||
<>{t('schemeManager.upload.currentScheme')}<strong>{selectedScheme.name}</strong></>
|
||||
) : (
|
||||
t('schemeManager.upload.noSchemeMessage')
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FileUploader
|
||||
|
|
|
|||
28
lightrag_webui/src/contexts/SchemeContext.tsx
Normal file
28
lightrag_webui/src/contexts/SchemeContext.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// contexts/SchemeContext.tsx
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Scheme } from '@/api/lightrag';
|
||||
|
||||
interface SchemeContextType {
|
||||
selectedScheme: Scheme | undefined;
|
||||
setSelectedScheme: (scheme: Scheme | undefined) => void;
|
||||
}
|
||||
|
||||
const SchemeContext = createContext<SchemeContextType | undefined>(undefined);
|
||||
|
||||
export const SchemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [selectedScheme, setSelectedScheme] = useState<Scheme | undefined>();
|
||||
|
||||
return (
|
||||
<SchemeContext.Provider value={{ selectedScheme, setSelectedScheme }}>
|
||||
{children}
|
||||
</SchemeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useScheme = () => {
|
||||
const context = useContext(SchemeContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useScheme must be used within a SchemeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -18,6 +18,8 @@ import UploadDocumentsDialog from '@/components/documents/UploadDocumentsDialog'
|
|||
import ClearDocumentsDialog from '@/components/documents/ClearDocumentsDialog'
|
||||
import DeleteDocumentsDialog from '@/components/documents/DeleteDocumentsDialog'
|
||||
import PaginationControls from '@/components/ui/PaginationControls'
|
||||
import { SchemeProvider } from '@/contexts/SchemeContext';
|
||||
import SchemeManager from '@/components/documents/SchemeManager/SchemeManager'
|
||||
|
||||
import {
|
||||
scanNewDocuments,
|
||||
|
|
@ -35,6 +37,8 @@ import { useBackendState } from '@/stores/state'
|
|||
import { RefreshCwIcon, ActivityIcon, ArrowUpIcon, ArrowDownIcon, RotateCcwIcon, CheckSquareIcon, XIcon, AlertTriangle, Info } from 'lucide-react'
|
||||
import PipelineStatusDialog from '@/components/documents/PipelineStatusDialog'
|
||||
|
||||
import { useScheme } from '@/contexts/SchemeContext';
|
||||
|
||||
type StatusFilter = DocStatus | 'all';
|
||||
|
||||
|
||||
|
|
@ -169,6 +173,8 @@ type SortField = 'created_at' | 'updated_at' | 'id' | 'file_path';
|
|||
type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export default function DocumentManager() {
|
||||
const { selectedScheme } = useScheme();
|
||||
|
||||
// Track component mount status
|
||||
const isMountedRef = useRef(true);
|
||||
|
||||
|
|
@ -230,6 +236,8 @@ export default function DocumentManager() {
|
|||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
ready: 1,
|
||||
handling: 1
|
||||
});
|
||||
|
||||
// State for document selection
|
||||
|
|
@ -296,6 +304,8 @@ export default function DocumentManager() {
|
|||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
ready: 1,
|
||||
handling: 1
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -441,7 +451,9 @@ export default function DocumentManager() {
|
|||
const prevStatusCounts = useRef({
|
||||
processed: 0,
|
||||
processing: 0,
|
||||
handling: 0,
|
||||
pending: 0,
|
||||
ready: 0,
|
||||
failed: 0
|
||||
})
|
||||
|
||||
|
|
@ -532,6 +544,8 @@ export default function DocumentManager() {
|
|||
processed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processed'),
|
||||
processing: response.documents.filter((doc: DocStatusResponse) => doc.status === 'processing'),
|
||||
pending: response.documents.filter((doc: DocStatusResponse) => doc.status === 'pending'),
|
||||
ready: response.documents.filter((doc: DocStatusResponse) => doc.status === 'ready'),
|
||||
handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling'),
|
||||
failed: response.documents.filter((doc: DocStatusResponse) => doc.status === 'failed')
|
||||
}
|
||||
};
|
||||
|
|
@ -794,7 +808,14 @@ export default function DocumentManager() {
|
|||
// Check if component is still mounted before starting the request
|
||||
if (!isMountedRef.current) return;
|
||||
|
||||
const { status, message, track_id: _track_id } = await scanNewDocuments(); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
if (!selectedScheme) {
|
||||
toast.error(t('documentPanel.documentManager.errors.missingSchemeId'));
|
||||
return;
|
||||
}
|
||||
|
||||
const schemeConfig = selectedScheme.config
|
||||
|
||||
const { status, message, track_id: _track_id } = await scanNewDocuments(schemeConfig); // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
|
||||
// Check again if component is still mounted after the request completes
|
||||
if (!isMountedRef.current) return;
|
||||
|
|
@ -820,10 +841,10 @@ export default function DocumentManager() {
|
|||
} catch (err) {
|
||||
// Only show error if component is still mounted
|
||||
if (isMountedRef.current) {
|
||||
toast.error(t('documentPanel.documentManager.errors.scanFailed', { error: errorMessage(err) }));
|
||||
toast.error(t('documentPanel.documentManager.errors.scanFiled', { error: errorMessage(err) }));
|
||||
}
|
||||
}
|
||||
}, [t, startPollingInterval, currentTab, health, statusCounts])
|
||||
}, [t, startPollingInterval, currentTab, health, statusCounts, selectedScheme])
|
||||
|
||||
// Handle page size change - update state and save to store
|
||||
const handlePageSizeChange = useCallback((newPageSize: number) => {
|
||||
|
|
@ -839,6 +860,8 @@ export default function DocumentManager() {
|
|||
processing: 1,
|
||||
pending: 1,
|
||||
failed: 1,
|
||||
ready: 1,
|
||||
handling: 1
|
||||
});
|
||||
|
||||
setPagination(prev => ({ ...prev, page: 1, page_size: newPageSize }));
|
||||
|
|
@ -878,6 +901,8 @@ export default function DocumentManager() {
|
|||
processed: response.documents.filter(doc => doc.status === 'processed'),
|
||||
processing: response.documents.filter(doc => doc.status === 'processing'),
|
||||
pending: response.documents.filter(doc => doc.status === 'pending'),
|
||||
ready: response.documents.filter((doc: DocStatusResponse) => doc.status === 'ready'),
|
||||
handling: response.documents.filter((doc: DocStatusResponse) => doc.status === 'handling'),
|
||||
failed: response.documents.filter(doc => doc.status === 'failed')
|
||||
}
|
||||
};
|
||||
|
|
@ -945,7 +970,9 @@ export default function DocumentManager() {
|
|||
const newStatusCounts = {
|
||||
processed: docs?.statuses?.processed?.length || 0,
|
||||
processing: docs?.statuses?.processing?.length || 0,
|
||||
handling: docs?.statuses?.handling?.length || 0,
|
||||
pending: docs?.statuses?.pending?.length || 0,
|
||||
ready: docs?.statuses?.ready?.length || 0,
|
||||
failed: docs?.statuses?.failed?.length || 0
|
||||
}
|
||||
|
||||
|
|
@ -1133,6 +1160,7 @@ export default function DocumentManager() {
|
|||
<ClearDocumentsDialog onDocumentsCleared={handleDocumentsCleared} />
|
||||
) : null}
|
||||
<UploadDocumentsDialog onDocumentsUploaded={fetchDocuments} />
|
||||
<SchemeManager />
|
||||
<PipelineStatusDialog
|
||||
open={showPipelineStatus}
|
||||
onOpenChange={setShowPipelineStatus}
|
||||
|
|
@ -1181,6 +1209,17 @@ export default function DocumentManager() {
|
|||
>
|
||||
{t('documentPanel.documentManager.status.processing')} ({statusCounts.PROCESSING || statusCounts.processing || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'handling' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('handling')}
|
||||
className={cn(
|
||||
documentCounts.handling > 0 ? 'text-purple-600' : 'text-gray-500',
|
||||
statusFilter === 'handling' && 'bg-purple-100 dark:bg-purple-900/30 font-medium border border-purple-400 dark:border-purple-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.handling')} ({statusCounts.HANDLING || statusCounts.handling || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'pending' ? 'secondary' : 'outline'}
|
||||
|
|
@ -1193,6 +1232,17 @@ export default function DocumentManager() {
|
|||
>
|
||||
{t('documentPanel.documentManager.status.pending')} ({statusCounts.PENDING || statusCounts.pending || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'ready' ? 'secondary' : 'outline'}
|
||||
onClick={() => setStatusFilter('ready')}
|
||||
className={cn(
|
||||
documentCounts.ready > 0 ? 'text-gray-600' : 'text-gray-500',
|
||||
statusFilter === 'ready' && 'bg-gray-100 dark:bg-gray-900/30 font-medium border border-gray-400 dark:border-gray-600 shadow-sm'
|
||||
)}
|
||||
>
|
||||
{t('documentPanel.documentManager.status.ready')} ({statusCounts.READY || statusCounts.ready || 0})
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant={statusFilter === 'failed' ? 'secondary' : 'outline'}
|
||||
|
|
@ -1273,6 +1323,7 @@ export default function DocumentManager() {
|
|||
</div>
|
||||
</TableHead>
|
||||
<TableHead>{t('documentPanel.documentManager.columns.summary')}</TableHead>
|
||||
<TableHead>{t('documentPanel.documentManager.columns.handler')}</TableHead>
|
||||
<TableHead>{t('documentPanel.documentManager.columns.status')}</TableHead>
|
||||
<TableHead>{t('documentPanel.documentManager.columns.length')}</TableHead>
|
||||
<TableHead>{t('documentPanel.documentManager.columns.chunks')}</TableHead>
|
||||
|
|
@ -1344,6 +1395,9 @@ export default function DocumentManager() {
|
|||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="truncate max-w-[150px]">
|
||||
{doc.scheme_name || '-'}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="group relative flex items-center overflow-visible tooltip-container">
|
||||
{doc.status === 'processed' && (
|
||||
|
|
@ -1358,6 +1412,12 @@ export default function DocumentManager() {
|
|||
{doc.status === 'failed' && (
|
||||
<span className="text-red-600">{t('documentPanel.documentManager.status.failed')}</span>
|
||||
)}
|
||||
{doc.status === 'ready' && (
|
||||
<span className="text-purple-600">{t('documentPanel.documentManager.status.ready')}</span>
|
||||
)}
|
||||
{doc.status === 'handling' && (
|
||||
<span className="text-gray-600">{t('documentPanel.documentManager.status.handling')}</span>
|
||||
)}
|
||||
|
||||
{/* Icon rendering logic */}
|
||||
{doc.error_msg ? (
|
||||
|
|
@ -1382,10 +1442,10 @@ export default function DocumentManager() {
|
|||
<TableCell>{doc.content_length ?? '-'}</TableCell>
|
||||
<TableCell>{doc.chunks_count ?? '-'}</TableCell>
|
||||
<TableCell className="truncate">
|
||||
{new Date(doc.created_at).toLocaleString()}
|
||||
{doc.created_at ? new Date(doc.created_at).toLocaleString() : '-'}
|
||||
</TableCell>
|
||||
<TableCell className="truncate">
|
||||
{new Date(doc.updated_at).toLocaleString()}
|
||||
{doc.updated_at ? new Date(doc.updated_at).toLocaleString() : '-'}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Checkbox
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"id": "المعرف",
|
||||
"fileName": "اسم الملف",
|
||||
"summary": "الملخص",
|
||||
"handler": "المعالج",
|
||||
"status": "الحالة",
|
||||
"length": "الطول",
|
||||
"chunks": "الأجزاء",
|
||||
|
|
@ -138,13 +139,16 @@
|
|||
"all": "الكل",
|
||||
"completed": "مكتمل",
|
||||
"processing": "قيد المعالجة",
|
||||
"handling": "استخراج",
|
||||
"pending": "معلق",
|
||||
"ready": "جاهز",
|
||||
"failed": "فشل"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "فشل تحميل المستندات\n{{error}}",
|
||||
"scanFailed": "فشل مسح المستندات\n{{error}}",
|
||||
"scanProgressFailed": "فشل الحصول على تقدم المسح\n{{error}}"
|
||||
"scanProgressFailed": "فشل الحصول على تقدم المسح\n{{error}}",
|
||||
"missingSchemeId": "الحل هو في عداد المفقودين ، حدد الحل"
|
||||
},
|
||||
"fileNameLabel": "اسم الملف",
|
||||
"showButton": "عرض",
|
||||
|
|
@ -422,5 +426,31 @@
|
|||
"prevPage": "الصفحة السابقة",
|
||||
"nextPage": "الصفحة التالية",
|
||||
"lastPage": "الصفحة الأخيرة"
|
||||
},
|
||||
"schemeManager": {
|
||||
"button": "مخططات معالجة المستندات",
|
||||
"title": "مدير المخططات",
|
||||
"description": "إنشاء مخططات جديدة وتكوين الخيارات",
|
||||
"schemeList": "قائمة المخططات",
|
||||
"schemeConfig": "تكوين المخطط",
|
||||
"inputPlaceholder": "أدخل اسم المخطط",
|
||||
"deleteTooltip": "حذف المخطط",
|
||||
"emptySchemes": "لا توجد مخططات متاحة",
|
||||
"selectSchemePrompt": "يرجى تحديد أو إنشاء مخطط أولاً",
|
||||
"processingFramework": "إطار المعالجة",
|
||||
"extractionTool": "أداة الاستخراج",
|
||||
"modelSource": "مصدر النموذج",
|
||||
"errors": {
|
||||
"loadFailed": "فشل تحميل المخططات",
|
||||
"nameEmpty": "لا يمكن أن يكون اسم المخطط فارغًا",
|
||||
"nameExists": "اسم المخطط موجود بالفعل",
|
||||
"addFailed": "فشل إضافة المخطط",
|
||||
"deleteFailed": "فشل حذف المخطط"
|
||||
},
|
||||
"upload": {
|
||||
"noSchemeSelected": "لم يتم تحديد مخطط معالجة، يرجى تحديد مخطط معالجة المستندات أولاً!",
|
||||
"currentScheme": "المخطط الحالي: ",
|
||||
"noSchemeMessage": "لم يتم تحديد مخطط معالجة، يرجى إضافة وتحديد واحد أولاً"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"id": "ID",
|
||||
"fileName": "File Name",
|
||||
"summary": "Summary",
|
||||
"handler": "Handler",
|
||||
"status": "Status",
|
||||
"length": "Length",
|
||||
"chunks": "Chunks",
|
||||
|
|
@ -138,13 +139,16 @@
|
|||
"all": "All",
|
||||
"completed": "Completed",
|
||||
"processing": "Processing",
|
||||
"handling": "Handling",
|
||||
"pending": "Pending",
|
||||
"ready": "Ready",
|
||||
"failed": "Failed"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "Failed to load documents\n{{error}}",
|
||||
"scanFailed": "Failed to scan documents\n{{error}}",
|
||||
"scanProgressFailed": "Failed to get scan progress\n{{error}}"
|
||||
"scanProgressFailed": "Failed to get scan progress\n{{error}}",
|
||||
"missingSchemeId": "Lack of solution, please select a solution"
|
||||
},
|
||||
"fileNameLabel": "File Name",
|
||||
"showButton": "Show",
|
||||
|
|
@ -422,5 +426,31 @@
|
|||
"prevPage": "Previous Page",
|
||||
"nextPage": "Next Page",
|
||||
"lastPage": "Last Page"
|
||||
},
|
||||
"schemeManager": {
|
||||
"button": "Document Processing Schemes",
|
||||
"title": "Scheme Manager",
|
||||
"description": "Create new schemes and configure options",
|
||||
"schemeList": "Scheme List",
|
||||
"schemeConfig": "Scheme Configuration",
|
||||
"inputPlaceholder": "Enter scheme name",
|
||||
"deleteTooltip": "Delete scheme",
|
||||
"emptySchemes": "No schemes available",
|
||||
"selectSchemePrompt": "Please select or create a scheme first",
|
||||
"processingFramework": "Processing Framework",
|
||||
"extractionTool": "Extraction Tool",
|
||||
"modelSource": "Model Source",
|
||||
"errors": {
|
||||
"loadFailed": "Failed to load schemes",
|
||||
"nameEmpty": "Scheme name cannot be empty",
|
||||
"nameExists": "Scheme name already exists",
|
||||
"addFailed": "Failed to add scheme",
|
||||
"deleteFailed": "Failed to delete scheme"
|
||||
},
|
||||
"upload": {
|
||||
"noSchemeSelected": "No processing scheme selected, please select a document processing scheme first!",
|
||||
"currentScheme": "Current scheme: ",
|
||||
"noSchemeMessage": "No processing scheme selected, please add and select one"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"id": "ID",
|
||||
"fileName": "Nom du fichier",
|
||||
"summary": "Résumé",
|
||||
"handler": "Gestionnaire",
|
||||
"status": "Statut",
|
||||
"length": "Longueur",
|
||||
"chunks": "Fragments",
|
||||
|
|
@ -138,13 +139,16 @@
|
|||
"all": "Tous",
|
||||
"completed": "Terminé",
|
||||
"processing": "En traitement",
|
||||
"handling": "Extraction",
|
||||
"pending": "En attente",
|
||||
"ready": "Prêt",
|
||||
"failed": "Échoué"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "Échec du chargement des documents\n{{error}}",
|
||||
"scanFailed": "Échec de la numérisation des documents\n{{error}}",
|
||||
"scanProgressFailed": "Échec de l'obtention de la progression de la numérisation\n{{error}}"
|
||||
"scanProgressFailed": "Échec de l'obtention de la progression de la numérisation\n{{error}}",
|
||||
"missingSchemeId": "Schéma de traitement manquant, veuillez sélectionner un schéma de traitement"
|
||||
},
|
||||
"fileNameLabel": "Nom du fichier",
|
||||
"showButton": "Afficher",
|
||||
|
|
@ -422,5 +426,31 @@
|
|||
"prevPage": "Page précédente",
|
||||
"nextPage": "Page suivante",
|
||||
"lastPage": "Dernière page"
|
||||
},
|
||||
"schemeManager": {
|
||||
"button": "Schémas de traitement de documents",
|
||||
"title": "Gestionnaire de schémas",
|
||||
"description": "Créer de nouveaux schémas et configurer les options",
|
||||
"schemeList": "Liste des schémas",
|
||||
"schemeConfig": "Configuration du schéma",
|
||||
"inputPlaceholder": "Entrer le nom du schéma",
|
||||
"deleteTooltip": "Supprimer le schéma",
|
||||
"emptySchemes": "Aucun schéma disponible",
|
||||
"selectSchemePrompt": "Veuillez d'abord sélectionner ou créer un schéma",
|
||||
"processingFramework": "Framework de traitement",
|
||||
"extractionTool": "Outil d'extraction",
|
||||
"modelSource": "Source du modèle",
|
||||
"errors": {
|
||||
"loadFailed": "Échec du chargement des schémas",
|
||||
"nameEmpty": "Le nom du schéma ne peut pas être vide",
|
||||
"nameExists": "Le nom du schéma existe déjà",
|
||||
"addFailed": "Échec de l'ajout du schéma",
|
||||
"deleteFailed": "Échec de la suppression du schéma"
|
||||
},
|
||||
"upload": {
|
||||
"noSchemeSelected": "Aucun schéma de traitement sélectionné, veuillez d'abord sélectionner un schéma de traitement de documents !",
|
||||
"currentScheme": "Schéma actuel : ",
|
||||
"noSchemeMessage": "Aucun schéma de traitement sélectionné, veuillez d'abord en ajouter et en sélectionner un"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"id": "ID",
|
||||
"fileName": "文件名",
|
||||
"summary": "摘要",
|
||||
"handler": "处理方案",
|
||||
"status": "状态",
|
||||
"length": "长度",
|
||||
"chunks": "分块",
|
||||
|
|
@ -138,13 +139,16 @@
|
|||
"all": "全部",
|
||||
"completed": "已完成",
|
||||
"processing": "处理中",
|
||||
"handling": "提取中",
|
||||
"pending": "等待中",
|
||||
"ready": "准备中",
|
||||
"failed": "失败"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "加载文档失败\n{{error}}",
|
||||
"scanFailed": "扫描文档失败\n{{error}}",
|
||||
"scanProgressFailed": "获取扫描进度失败\n{{error}}"
|
||||
"scanProgressFailed": "获取扫描进度失败\n{{error}}",
|
||||
"missingSchemeId": "缺少处理方案,请选择处理方案"
|
||||
},
|
||||
"fileNameLabel": "文件名",
|
||||
"showButton": "显示",
|
||||
|
|
@ -422,5 +426,31 @@
|
|||
"prevPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"lastPage": "末页"
|
||||
},
|
||||
"schemeManager": {
|
||||
"button": "文档处理方案",
|
||||
"title": "方案管理器",
|
||||
"description": "创建新方案并配置选项",
|
||||
"schemeList": "方案列表",
|
||||
"schemeConfig": "方案配置",
|
||||
"inputPlaceholder": "输入方案名称",
|
||||
"deleteTooltip": "删除方案",
|
||||
"emptySchemes": "暂无方案",
|
||||
"selectSchemePrompt": "请先选择或创建一个方案",
|
||||
"processingFramework": "处理框架",
|
||||
"extractionTool": "提取工具",
|
||||
"modelSource": "模型源",
|
||||
"errors": {
|
||||
"loadFailed": "加载方案失败",
|
||||
"nameEmpty": "方案名称不能为空",
|
||||
"nameExists": "方案名称已存在",
|
||||
"addFailed": "添加方案失败",
|
||||
"deleteFailed": "删除方案失败"
|
||||
},
|
||||
"upload": {
|
||||
"noSchemeSelected": "未选择处理方案,请先选择文档处理方案!",
|
||||
"currentScheme": "当前方案:",
|
||||
"noSchemeMessage": "未选择处理方案,请先添加选择"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@
|
|||
"id": "ID",
|
||||
"fileName": "檔案名稱",
|
||||
"summary": "摘要",
|
||||
"handler": "處理方案",
|
||||
"status": "狀態",
|
||||
"length": "長度",
|
||||
"chunks": "分塊",
|
||||
|
|
@ -138,13 +139,16 @@
|
|||
"all": "全部",
|
||||
"completed": "已完成",
|
||||
"processing": "處理中",
|
||||
"handling": "提取中",
|
||||
"pending": "等待中",
|
||||
"ready": "準備中",
|
||||
"failed": "失敗"
|
||||
},
|
||||
"errors": {
|
||||
"loadFailed": "載入文件失敗\n{{error}}",
|
||||
"scanFailed": "掃描文件失敗\n{{error}}",
|
||||
"scanProgressFailed": "取得掃描進度失敗\n{{error}}"
|
||||
"scanProgressFailed": "取得掃描進度失敗\n{{error}}",
|
||||
"missingSchemeId": "缺少處理方案,請選擇處理方案"
|
||||
},
|
||||
"fileNameLabel": "檔案名稱",
|
||||
"showButton": "顯示",
|
||||
|
|
@ -422,5 +426,31 @@
|
|||
"prevPage": "上一頁",
|
||||
"nextPage": "下一頁",
|
||||
"lastPage": "最後一頁"
|
||||
},
|
||||
"schemeManager": {
|
||||
"button": "文件處理方案",
|
||||
"title": "方案管理器",
|
||||
"description": "建立新方案並設定選項",
|
||||
"schemeList": "方案清單",
|
||||
"schemeConfig": "方案設定",
|
||||
"inputPlaceholder": "輸入方案名稱",
|
||||
"deleteTooltip": "刪除方案",
|
||||
"emptySchemes": "暫無方案",
|
||||
"selectSchemePrompt": "請先選擇或建立一個方案",
|
||||
"processingFramework": "處理框架",
|
||||
"extractionTool": "提取工具",
|
||||
"modelSource": "模型源",
|
||||
"errors": {
|
||||
"loadFailed": "載入方案失敗",
|
||||
"nameEmpty": "方案名稱不能為空",
|
||||
"nameExists": "方案名稱已存在",
|
||||
"addFailed": "新增方案失敗",
|
||||
"deleteFailed": "刪除方案失敗"
|
||||
},
|
||||
"upload": {
|
||||
"noSchemeSelected": "未選擇處理方案,請先選擇文件處理方案!",
|
||||
"currentScheme": "目前方案:",
|
||||
"noSchemeMessage": "未選擇處理方案,請先新增選擇"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
content: [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue