Files
rag-manager/ingest_pipeline/cli/tui/utils/runners.py
2025-09-19 06:56:19 +00:00

147 lines
4.9 KiB
Python

"""TUI runner functions and initialization."""
from __future__ import annotations
import asyncio
import logging
from logging import Logger
from logging.handlers import QueueHandler, RotatingFileHandler
from pathlib import Path
from queue import Queue
from typing import NamedTuple
from ....config import configure_prefect, get_settings
from ....core.models import StorageBackend
from .storage_manager import StorageManager
class _TuiLoggingContext(NamedTuple):
"""Container describing configured logging outputs for the TUI."""
queue: Queue[logging.LogRecord]
formatter: logging.Formatter
log_file: Path | None
_logging_context: _TuiLoggingContext | None = None
def _configure_tui_logging(*, log_level: str) -> _TuiLoggingContext:
"""Configure logging so that messages do not break the TUI output."""
global _logging_context
if _logging_context is not None:
return _logging_context
resolved_level = getattr(logging, log_level.upper(), logging.INFO)
log_queue: Queue[logging.LogRecord] = Queue()
formatter = logging.Formatter(
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
root_logger = logging.getLogger()
root_logger.setLevel(resolved_level)
# Remove existing stream handlers to prevent console flicker inside the TUI
for handler in list(root_logger.handlers):
root_logger.removeHandler(handler)
queue_handler = QueueHandler(log_queue)
queue_handler.setLevel(resolved_level)
root_logger.addHandler(queue_handler)
log_file: Path | None = None
try:
log_dir = Path.cwd() / "logs"
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / "tui.log"
file_handler = RotatingFileHandler(
log_file,
maxBytes=2_000_000,
backupCount=5,
encoding="utf-8",
)
file_handler.setLevel(resolved_level)
file_handler.setFormatter(formatter)
root_logger.addHandler(file_handler)
except OSError as exc: # pragma: no cover - filesystem specific
fallback = logging.getLogger(__name__)
fallback.warning("Failed to configure file logging for TUI: %s", exc)
_logging_context = _TuiLoggingContext(log_queue, formatter, log_file)
return _logging_context
LOGGER: Logger = logging.getLogger(__name__)
async def run_textual_tui() -> None:
"""Run the enhanced modern TUI with better error handling and initialization."""
settings = get_settings()
configure_prefect(settings)
logging_context = _configure_tui_logging(log_level=settings.log_level)
LOGGER.info("Initializing collection management TUI")
LOGGER.info("Scanning available storage backends")
# Initialize storage manager
storage_manager = StorageManager(settings)
backend_status = await storage_manager.initialize_all_backends()
# Report initialization results
for backend, success in backend_status.items():
if success:
LOGGER.info("%s connected successfully", backend.value)
else:
LOGGER.warning("%s connection failed", backend.value)
available_backends = storage_manager.get_available_backends()
if not available_backends:
LOGGER.error("Could not connect to any storage backend")
LOGGER.info("Please check your configuration and try again")
LOGGER.info("Supported backends: Weaviate, OpenWebUI, R2R")
return
LOGGER.info(
"Launching TUI with %d backend(s): %s",
len(available_backends),
", ".join(backend.value for backend in available_backends),
)
# Get individual storage instances for backward compatibility
from ....storage.openwebui import OpenWebUIStorage
from ....storage.weaviate import WeaviateStorage
weaviate_backend = storage_manager.get_backend(StorageBackend.WEAVIATE)
openwebui_backend = storage_manager.get_backend(StorageBackend.OPEN_WEBUI)
r2r_backend = storage_manager.get_backend(StorageBackend.R2R)
# Type-safe casting to specific storage types
weaviate = weaviate_backend if isinstance(weaviate_backend, WeaviateStorage) else None
openwebui = openwebui_backend if isinstance(openwebui_backend, OpenWebUIStorage) else None
# Import here to avoid circular import
from ..app import CollectionManagementApp
app = CollectionManagementApp(
storage_manager,
weaviate,
openwebui,
r2r_backend,
log_queue=logging_context.queue,
log_formatter=logging_context.formatter,
log_file=logging_context.log_file,
)
try:
await app.run_async()
finally:
LOGGER.info("Shutting down storage connections")
await storage_manager.close_all()
LOGGER.info("All storage connections closed gracefully")
def dashboard() -> None:
"""Launch the modern collection dashboard."""
asyncio.run(run_textual_tui())