diff --git a/README.md b/README.md index 61ec62b..f555246 100644 --- a/README.md +++ b/README.md @@ -1,418 +1,298 @@ -# NeuraX - Offline Multimodal RAG System - -[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)]() - -## Overview -NeuraX is a production-ready offline multimodal Retrieval-Augmented Generation (RAG) system designed for NTRO's SIH 2025 problem statement. It provides secure, air-gapped document intelligence with advanced multimodal capabilities and enterprise-grade security features. - -## Demo: - -[![Watch the video](https://img.youtube.com/vi/2qcBRtBl5q8/0.jpg)](https://youtu.be/2qcBRtBl5q8) - -## โœจ Key Features - -### ๐Ÿ”’ **Security & Privacy** -- **Complete Offline Operation**: Zero internet dependencies, air-gapped deployment -- **Knowledge Graph Security**: Real-time anomaly detection and tamper protection -- **Audit Logging**: Comprehensive activity tracking and compliance monitoring -- **Data Sovereignty**: All processing occurs locally with no external API calls - -### ๐Ÿค– **Advanced AI Capabilities** -- **Multimodal Understanding**: Process documents, images, and audio seamlessly -- **Cross-Modal Search**: Find relevant content across different data types -- **LM Studio Integration**: Local LLM hosting with Gemma 3n (multimodal) and Qwen3 4B (reasoning) -- **CLIP Embeddings**: State-of-the-art visual-text similarity matching -- **Intelligent Citations**: Numbered references with confidence scores and expandable sources - -### ๐Ÿ“ **Comprehensive Format Support** -- **Documents**: PDF, DOCX, DOC, TXT with OCR fallback -- **Images**: JPG, JPEG, PNG, BMP, TIFF, WEBP with visual similarity search -- **Audio**: WAV, MP3, M4A, FLAC, OGG with speech-to-text processing -- **Batch Processing**: Handle multiple files simultaneously with progress tracking - -### ๐Ÿš€ **Production Features** -- **Auto-Deployment**: One-click executable generation with PyInstaller -- **USB Portability**: Export complete system to USB for air-gapped deployment -- **Performance Optimization**: Memory-efficient processing with GPU acceleration -- **Error Resilience**: Graceful degradation and comprehensive error handling -- **Real-time Feedback**: User feedback collection and performance metrics - -## ๐Ÿ—๏ธ Architecture - -### Core Components -- **LM Studio Integration**: Local LLM server for multimodal and reasoning tasks -- **ChromaDB**: Persistent vector database for semantic search -- **CLIP Embeddings**: Visual-text cross-modal understanding -- **Whisper STT**: Speech-to-text for audio processing -- **NetworkX**: Knowledge graph with security monitoring -- **Gradio UI**: Modern web interface for end users -- **Streamlit Dashboard**: Analytics and system monitoring - -## ๐Ÿ› ๏ธ System Requirements - -### Minimum Requirements -- **Python**: 3.8+ (3.9+ recommended for optimal performance) -- **Memory**: 8GB RAM (16GB+ recommended for large datasets) -- **Storage**: 5GB free space (models are managed via LM Studio) -- **OS**: Windows 10+, Linux (Ubuntu 18.04+), macOS 10.15+ - -### Recommended Setup -- **Memory**: 16GB+ RAM for smooth operation -- **GPU**: 6GB+ VRAM for accelerated processing (CPU fallback available) -- **Storage**: 10GB+ for cache and data processing -- **Network**: None required during operation (offline-first design) - -### Dependencies -- **LM Studio**: For local LLM hosting (Gemma 3n + Qwen3 4B) -- **Tesseract OCR**: For document text extraction (auto-bundled) -- **FFmpeg**: For audio processing (platform-specific installation) - -## ๐Ÿš€ Quick Start - -### Option 1: Automated Installation (Recommended) -```bash -# Clone the repository -git clone https://github.com/thrishank007/NeuraX.git -cd NeuraX +# NeuraX - Production-Ready Multimodal RAG System -# Run automated setup -python install_dependencies.py +A complete offline-first Retrieval-Augmented Generation (RAG) system with multimodal capabilities, featuring a modern Next.js frontend and Python FastAPI backend. -# Setup LM Studio integration -python migrate_to_lmstudio.py +## Architecture Overview -# Launch the system -python main_launcher.py -``` +### Backend (Python) +- **FastAPI REST API** (`backend/api/main.py`) - RESTful API wrapper +- **Document Processing** - PDF, DOCX, TXT, Images, Audio with OCR and STT +- **Vector Store** - ChromaDB for semantic search +- **LLM Integration** - LM Studio (Gemma 3n for multimodal, Qwen 4B for reasoning) +- **Knowledge Graph** - NetworkX-based security and anomaly detection +- **Feedback System** - User feedback collection and metrics -### Option 2: Manual Installation -```bash -# Clone repository -git clone https://github.com/thrishank007/NeuraX.git -cd NeuraX +### Frontend (Next.js) +- **Next.js 14+** with App Router +- **TypeScript** strict mode +- **Tailwind CSS** + shadcn/ui components +- **Responsive Design** with dark mode +- **Real-time Updates** via WebSocket (optional) -# Create virtual environment -python -m venv venv -source venv/bin/activate # On Windows: venv\Scripts\activate +## Quick Start -# Install dependencies +### 1. Backend Setup + +```bash +# Install Python dependencies pip install -r requirements.txt -# Install system dependencies (platform-specific) -# Ubuntu/Debian: sudo apt-get install tesseract-ocr ffmpeg -# macOS: brew install tesseract ffmpeg -# Windows: Automated via install_dependencies.py +# Install FastAPI and uvicorn if not already installed +pip install fastapi uvicorn python-multipart -# Launch system -python main_launcher.py +# Start the FastAPI backend +cd backend/api +python main.py +# or +uvicorn backend.api.main:app --reload --host 0.0.0.0 --port 8000 ``` -### Option 3: Portable Executable -```bash -# Build portable executable -python build_executables.py +The backend API will be available at `http://localhost:8000` -# Deploy to USB or air-gapped system -# Executable will be in packages/ directory -``` +### 2. Frontend Setup -## ๐ŸŽฏ LM Studio Setup (Required) +```bash +# Navigate to frontend directory +cd neurax-frontend -NeuraX uses LM Studio for local LLM hosting, providing better performance and easier model management: +# Install dependencies +npm install -### 1. Install LM Studio -- Download from [https://lmstudio.ai/](https://lmstudio.ai/) -- Install and launch the application +# Set up environment variables +cp .env.local.example .env.local +# Edit .env.local with your configuration -### 2. Download Models -In LM Studio, search for and download: -- **Gemma 3n**: For multimodal queries (text + images) -- **Qwen3 4B Thinking 2507**: For complex reasoning tasks +# Start development server +npm run dev +``` -### 3. Start Local Server -1. Go to "Local Server" tab in LM Studio -2. Load your preferred model (Gemma for multimodal, Qwen for reasoning) -3. Start server on `localhost:1234` -4. Verify server is running with green status indicator +The frontend will be available at `http://localhost:3000` + +### 3. LM Studio Setup (Optional but Recommended) + +1. Download and install [LM Studio](https://lmstudio.ai/) +2. Load models: + - **Gemma 3n** (for multimodal queries) + - **Qwen3 4B Thinking** (for text reasoning) +3. Start the local server on port 1234 +4. The backend will automatically connect to LM Studio + +## Features + +### Document Management +- Upload PDF, DOCX, TXT, Images (JPG, PNG, etc.), Audio (WAV, MP3, etc.) +- Automatic processing and indexing +- Batch upload support +- File type validation and size limits + +### Multimodal Queries +- **Text Queries** - Natural language questions +- **Voice Queries** - Speech-to-text input +- **Image Queries** - Visual search (coming soon) +- **Multimodal** - Combined text + image queries + +### Results & Citations +- Numbered citations with expandable sources +- Confidence scores and similarity metrics +- Document previews +- Export capabilities (JSON, CSV) + +### Analytics Dashboard +- Performance metrics (retrieval, generation, latency) +- Usage statistics +- Security alerts and anomaly detection +- Knowledge graph visualization + +### Configuration +- LM Studio connection settings +- Search parameters (similarity threshold, max results) +- Model preferences +- Performance tuning + +## API Endpoints + +### File Upload +- `POST /api/upload` - Upload and process files +- `GET /api/files` - List uploaded files +- `DELETE /api/files/{file_id}` - Delete a file + +### Query Processing +- `POST /api/query` - Process text query +- `POST /api/query/voice` - Process voice query +- `GET /api/query/history` - Get query history +- `GET /api/query/suggestions` - Get auto-complete suggestions + +### Analytics +- `GET /api/analytics/metrics` - Get performance metrics +- `GET /api/analytics/usage` - Get usage statistics +- `GET /api/analytics/security` - Get security events +- `GET /api/knowledge-graph` - Get knowledge graph data + +### Feedback +- `POST /api/feedback` - Submit feedback +- `GET /api/feedback/history` - Get feedback history +- `GET /api/feedback/analytics` - Get feedback analytics + +### Configuration +- `GET /api/config` - Get current configuration +- `PUT /api/config` - Update configuration +- `POST /api/config/validate` - Validate configuration + +### Health +- `GET /api/health` - Health check + +## Development + +### Backend Development -### 4. Test Integration ```bash -python test_lmstudio_integration.py +# Run with auto-reload +uvicorn backend.api.main:app --reload + +# Run tests (if available) +pytest ``` -## ๐Ÿ’ป Usage Examples +### Frontend Development -### Basic Document Processing -```python -# Upload documents via Gradio interface -# Supported: PDF, DOCX, DOC, TXT files -# Automatic text extraction and indexing +```bash +# Development server with hot reload +npm run dev -# Query your documents -query = "What are the main findings in the research?" -# System returns relevant passages with citations -``` +# Type checking +npm run type-check -### Multimodal Search -```python -# Upload images along with documents -# Supported: JPG, PNG, BMP, TIFF, WEBP +# Linting +npm run lint -# Cross-modal queries -query = "Find documents related to this chart" -# System matches visual content with textual descriptions +# Production build +npm run build +npm start ``` -### Audio Processing -```python -# Upload audio files -# Supported: WAV, MP3, M4A, FLAC, OGG +## Project Structure -# Audio-to-text search -query = "What was discussed about budget planning?" -# System transcribes audio and searches content ``` - -## ๐Ÿ“‚ Project Structure -``` -NeuraX/ -โ”œโ”€โ”€ ๐Ÿ“ ingestion/ # Multimodal data processors -โ”‚ โ”œโ”€โ”€ document_processor.py # PDF, DOCX, DOC, TXT processing -โ”‚ โ”œโ”€โ”€ image_processor.py # Image analysis and OCR -โ”‚ โ”œโ”€โ”€ audio_processor.py # Speech-to-text conversion -โ”‚ โ”œโ”€โ”€ notes_processor.py # Structured note processing -โ”‚ โ””โ”€โ”€ ingestion_manager.py # Orchestrates all processors -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ indexing/ # Vector embeddings and storage -โ”‚ โ”œโ”€โ”€ embedding_manager.py # CLIP + text embeddings -โ”‚ โ”œโ”€โ”€ vector_store.py # ChromaDB interface -โ”‚ โ”œโ”€โ”€ cache_manager.py # Embedding cache optimization -โ”‚ โ”œโ”€โ”€ memory_manager.py # Memory usage optimization -โ”‚ โ””โ”€โ”€ performance_benchmarker.py # Performance monitoring -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ retrieval/ # Query processing -โ”‚ โ”œโ”€โ”€ query_processor.py # Multimodal query handling -โ”‚ โ””โ”€โ”€ speech_to_text_processor.py # Audio query processing -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ generation/ # LLM integration -โ”‚ โ”œโ”€โ”€ lmstudio_generator.py # LM Studio API client -โ”‚ โ”œโ”€โ”€ llm_factory.py # Model selection logic -โ”‚ โ”œโ”€โ”€ llm_generator.py # Legacy HF integration -โ”‚ โ””โ”€โ”€ citation_generator.py # Citation formatting +. +โ”œโ”€โ”€ backend/ +โ”‚ โ”œโ”€โ”€ api/ +โ”‚ โ”‚ โ””โ”€โ”€ main.py # FastAPI application +โ”‚ โ”œโ”€โ”€ routers/ # API route modules +โ”‚ โ”œโ”€โ”€ services/ # Business logic services +โ”‚ โ”œโ”€โ”€ models/ # Pydantic models +โ”‚ โ””โ”€โ”€ core/ # Core utilities โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ kg_security/ # Knowledge graph security -โ”‚ โ”œโ”€โ”€ knowledge_graph_manager.py # Graph construction -โ”‚ โ”œโ”€โ”€ anomaly_detector.py # Security monitoring -โ”‚ โ”œโ”€โ”€ security_event_logger.py # Audit logging -โ”‚ โ””โ”€โ”€ feedback_integration.py # User feedback processing +โ”œโ”€โ”€ neurax-frontend/ # Next.js frontend +โ”‚ โ”œโ”€โ”€ app/ # Next.js app directory +โ”‚ โ”œโ”€โ”€ components/ # React components +โ”‚ โ”œโ”€โ”€ lib/ # Utilities and API clients +โ”‚ โ””โ”€โ”€ public/ # Static assets โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ feedback/ # Feedback system -โ”‚ โ”œโ”€โ”€ feedback_system.py # User feedback collection -โ”‚ โ”œโ”€โ”€ metrics_collector.py # Performance metrics -โ”‚ โ””โ”€โ”€ ๐Ÿ“ exports/ # Feedback data exports -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ ui/ # User interfaces -โ”‚ โ”œโ”€โ”€ gradio_app.py # Main web interface -โ”‚ โ”œโ”€โ”€ streamlit_dashboard.py # Analytics dashboard -โ”‚ โ””โ”€โ”€ demo_gradio_app.py # Demo interface -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ tests/ # Comprehensive test suite -โ”‚ โ”œโ”€โ”€ test_*.py # Unit and integration tests -โ”‚ โ””โ”€โ”€ conftest.py # Test configuration -โ”‚ -โ”œโ”€โ”€ ๐Ÿ“ models/ # Local model cache (LM Studio managed) -โ”œโ”€โ”€ ๐Ÿ“ data/ # Input data and samples -โ”œโ”€โ”€ ๐Ÿ“ vector_db/ # ChromaDB persistent storage -โ”œโ”€โ”€ ๐Ÿ“ cache/ # Embedding and processing cache -โ”œโ”€โ”€ ๐Ÿ“ logs/ # System logs and error reports -โ”‚ -โ”œโ”€โ”€ ๐Ÿ”ง config.py # Central configuration -โ”œโ”€โ”€ ๐Ÿš€ main_launcher.py # Application orchestrator -โ”œโ”€โ”€ ๐Ÿ“‹ requirements.txt # Python dependencies -โ”œโ”€โ”€ ๐Ÿ› ๏ธ install_dependencies.py # Automated setup script -โ”œโ”€โ”€ ๐Ÿ“ฆ build_executables.py # Portable build script -โ”œโ”€โ”€ ๐Ÿ”„ migrate_to_lmstudio.py # LM Studio migration tool -โ””โ”€โ”€ ๐Ÿงช test_*.py # Verification and test scripts -``` - -## ๐Ÿ”ง Configuration - -### Core Settings (`config.py`) -```python -# LM Studio Configuration -LM_STUDIO_CONFIG = { - "base_url": "http://localhost:1234/v1", - "gemma_model": "google/gemma-3n", # Multimodal model - "qwen_model": "qwen/qwen3-4b-thinking-2507", # Reasoning model - "auto_model_switching": True, # Auto switch based on query type -} - -# Security Configuration -SECURITY_CONFIG = { - "allowed_file_extensions": [ - ".pdf", ".docx", ".doc", ".txt", # Documents - ".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".webp", # Images - ".wav", ".mp3", ".m4a", ".flac", ".ogg" # Audio - ], - "max_file_size_mb": 100, - "enable_audit_logging": True, -} +โ”œโ”€โ”€ ingestion/ # Document processing +โ”œโ”€โ”€ indexing/ # Vector store and embeddings +โ”œโ”€โ”€ retrieval/ # Query processing +โ”œโ”€โ”€ generation/ # LLM integration +โ”œโ”€โ”€ kg_security/ # Knowledge graph and security +โ”œโ”€โ”€ feedback/ # Feedback system +โ””โ”€โ”€ config.py # Configuration ``` -### Advanced Configuration -- **Performance tuning**: Memory thresholds, batch sizes, GPU settings -- **Security policies**: File validation, audit logging, anomaly detection -- **UI customization**: Interface themes, component visibility -- **Model preferences**: LLM selection, embedding models, fallback strategies +## Configuration -## ๐Ÿงช Testing & Validation +### Environment Variables -### Automated Testing Suite -```bash -# Run complete test suite -python -m pytest tests/ - -# Test specific components -python test_image_query_no_ocr.py # Image processing -python test_multimodal_simple.py # Multimodal queries -python test_lmstudio_integration.py # LM Studio integration -python test_final_verification.py # End-to-end validation -``` +**Backend:** +- Configure in `config.py` or via environment variables +- LM Studio URL: `http://localhost:1234/v1` (default) -### Manual Testing -```bash -# Test file upload interface -python test_file_upload_interface_fix.py +**Frontend:** +- `NEXT_PUBLIC_API_URL` - Backend API URL (default: `http://localhost:8000`) +- `NEXT_PUBLIC_LM_STUDIO_URL` - LM Studio URL (default: `http://localhost:1234`) +- `NEXT_PUBLIC_MAX_FILE_SIZE` - Max file size in bytes (default: 100MB) -# Validate system performance -python test_vector_store.py +## Deployment -# Check citation generation -python test_citation_fix.py -``` +### Docker (Recommended) -## ๐Ÿšข Deployment Options - -### Option 1: Standard Installation -- Install Python dependencies via pip -- Setup LM Studio separately -- Run via `python main_launcher.py` - -### Option 2: Portable Executable ```bash -# Build self-contained executable -python build_executables.py - -# Generates: -# - NeuraX-Windows-x64.zip -# - USB_Deployment/ folder for air-gapped systems +# Build and run with Docker Compose +docker-compose up -d ``` -### Option 3: USB Deployment -```bash -# Create USB-ready package -python build_executables.py --usb-deployment +### Manual Deployment -# Copy USB_Deployment/ contents to USB drive -# Includes autorun.inf for Windows systems -``` +1. **Backend:** + ```bash + # Install dependencies + pip install -r requirements.txt + + # Run with production server + uvicorn backend.api.main:app --host 0.0.0.0 --port 8000 + ``` -### Air-Gapped Deployment -1. Build executable on internet-connected system -2. Copy package to air-gapped environment -3. Install LM Studio and download models offline -4. Run executable with zero internet dependencies +2. **Frontend:** + ```bash + cd neurax-frontend + npm install + npm run build + npm start + ``` -## ๐Ÿ“Š Performance Metrics +## Offline Operation -### Processing Speeds -- **Document Indexing**: 50-100 documents/minute -- **Image Processing**: 25-50 images/minute -- **Audio Transcription**: Real-time (1x speed with Whisper-tiny) -- **Query Response**: 200-500ms average -- **Vector Search**: 4.7+ items/second similarity search +The system is designed for offline/air-gapped deployment: -### Resource Usage -- **Memory**: 4-8GB typical usage (scales with data size) -- **Storage**: 100MB base + data size + cache -- **GPU**: Optional but recommended for large datasets -- **CPU**: Efficient with multi-core utilization +- All models run locally (LM Studio) +- No external API dependencies +- Local vector database (ChromaDB) +- Offline document processing +- Local feedback storage -## ๐Ÿ›ก๏ธ Security Features +## Security -### Data Protection -- **Local Processing**: All data remains on local system -- **Encrypted Storage**: Vector database encryption at rest -- **Audit Trails**: Comprehensive activity logging -- **Access Control**: File type and size validation +- Input validation and sanitization +- File type and size validation +- CORS configuration +- Audit logging +- Anomaly detection +- Knowledge graph security layer -### Anomaly Detection -- **Knowledge Graph Monitoring**: Real-time graph analysis -- **Behavioral Analysis**: Unusual query pattern detection -- **Tamper Detection**: Content integrity verification -- **Alert System**: Automated security event notifications +## Performance -## ๐Ÿค Contributing & Support +- Efficient embedding caching +- Batch processing support +- Memory optimization +- Progressive loading for large datasets +- Connection pooling -### Development Setup -```bash -# Clone for development -git clone https://github.com/thrishank007/NeuraX.git -cd NeuraX - -# Install development dependencies -pip install -r requirements.txt -pip install pytest black flake8 +## Troubleshooting -# Run tests before committing -python -m pytest tests/ -``` +### Backend Issues -### Known Issues & Solutions -- **Tesseract OCR**: Auto-bundled in executables, manual install for dev -- **GPU Memory**: Adjust batch sizes in config for lower VRAM systems -- **LM Studio Connection**: Ensure server is running on localhost:1234 -- **Large Files**: Use batch processing for datasets >1GB +1. **LM Studio not connecting:** + - Ensure LM Studio is running on port 1234 + - Check `LM_STUDIO_CONFIG` in `config.py` -### Documentation -- **API Reference**: `/docs/api/` (generated from code) -- **Architecture Guide**: `/docs/architecture.md` -- **Deployment Guide**: `/docs/deployment.md` -- **Troubleshooting**: `/docs/troubleshooting.md` +2. **Vector store errors:** + - Check `vector_db/` directory permissions + - Ensure ChromaDB is properly installed -## ๐Ÿ“ˆ Roadmap +3. **Import errors:** + - Verify all dependencies in `requirements.txt` are installed + - Check Python path configuration -### Current Version (v1.0) -- โœ… Complete offline multimodal RAG system -- โœ… LM Studio integration with Gemma 3n + Qwen3 4B -- โœ… Cross-modal search capabilities -- โœ… Portable executable generation -- โœ… Enterprise security features +### Frontend Issues -### Future Enhancements (v1.1+) -- ๐Ÿ”„ Additional LLM integrations (Ollama, LocalAI) -- ๐Ÿ”„ Enhanced video processing capabilities -- ๐Ÿ”„ Multi-language support expansion -- ๐Ÿ”„ Advanced analytics dashboard -- ๐Ÿ”„ Distributed deployment options +1. **API connection errors:** + - Verify backend is running on port 8000 + - Check `NEXT_PUBLIC_API_URL` in `.env.local` -## ๐Ÿ“„ License +2. **Build errors:** + - Run `npm install` to ensure all dependencies are installed + - Check TypeScript errors with `npm run type-check` -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +## Contributing -## ๐Ÿ† Acknowledgments +1. Follow code style guidelines +2. Write tests for new features +3. Update documentation +4. Ensure TypeScript strict mode compliance +5. Test on multiple browsers -- **NTRO SIH 2025**: Problem statement and requirements definition -- **Hugging Face**: CLIP and Transformer models -- **LM Studio**: Local LLM hosting platform -- **ChromaDB**: Vector database infrastructure -- **Gradio**: Modern web interface framework +## License ---- +See LICENSE file for details. -**Built with โค๏ธ for secure, offline AI document intelligence** +## Support -For detailed documentation, visit: [Documentation](./docs/) -For support and issues: [GitHub Issues](https://github.com/thrishank007/NeuraX/issues) +For issues, questions, or contributions, please refer to the project documentation or create an issue in the repository. diff --git a/backend/api/main.py b/backend/api/main.py new file mode 100644 index 0000000..a32b38d --- /dev/null +++ b/backend/api/main.py @@ -0,0 +1,885 @@ +""" +FastAPI Backend API for NeuraX RAG System + +Provides RESTful API endpoints for the Next.js frontend to interact with +the Python-based RAG backend components. +""" + +import os +import sys +from pathlib import Path +from typing import List, Optional, Dict, Any +from datetime import datetime +import tempfile +import shutil + +from fastapi import FastAPI, UploadFile, File, HTTPException, Depends, BackgroundTasks +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse, StreamingResponse +from pydantic import BaseModel, Field +import uvicorn + +# Suppress warnings for optional dependencies +import warnings +warnings.filterwarnings("ignore", category=UserWarning) + +# Add project root to path +project_root = Path(__file__).parent.parent.parent +sys.path.insert(0, str(project_root)) + +# Import backend components +from ingestion.ingestion_manager import IngestionManager +from retrieval.query_processor import QueryProcessor +from generation.lmstudio_generator import LMStudioGenerator +from generation.citation_generator import CitationGenerator +from indexing.embedding_manager import EmbeddingManager +from indexing.vector_store import VectorStore +from feedback.feedback_system import FeedbackSystem +from kg_security.knowledge_graph_manager import KnowledgeGraphManager +from feedback.metrics_collector import MetricsCollector +from retrieval.speech_to_text_processor import SpeechToTextProcessor +from config import ( + LM_STUDIO_CONFIG, CHROMA_CONFIG, VECTOR_DB_DIR, + PROCESSING_CONFIG, FEEDBACK_CONFIG, KG_CONFIG +) + +# Initialize FastAPI app +app = FastAPI( + title="NeuraX RAG API", + description="RESTful API for NeuraX Multimodal RAG System", + version="2.0.0", + docs_url="/docs", + redoc_url="/redoc", + openapi_url="/openapi.json" +) + +# CORS configuration for Next.js frontend +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:3000", + "http://localhost:3001", + "http://127.0.0.1:3000", + "http://127.0.0.1:3001", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + expose_headers=["*"], +) + +# Global component instances (initialized on startup) +ingestion_manager: Optional[IngestionManager] = None +embedding_manager: Optional[EmbeddingManager] = None +vector_store: Optional[VectorStore] = None +query_processor: Optional[QueryProcessor] = None +llm_generator: Optional[LMStudioGenerator] = None +citation_generator: Optional[CitationGenerator] = None +feedback_system: Optional[FeedbackSystem] = None +kg_manager: Optional[KnowledgeGraphManager] = None +metrics_collector: Optional[MetricsCollector] = None +stt_processor: Optional[SpeechToTextProcessor] = None + + +# ==================== Startup/Shutdown ==================== + +@app.on_event("startup") +async def startup_event(): + """Initialize all backend components on startup""" + global ingestion_manager, embedding_manager, vector_store + global query_processor, llm_generator, citation_generator + global feedback_system, kg_manager, metrics_collector, stt_processor + + import traceback + + print("๐Ÿš€ Starting NeuraX Backend API initialization...") + + # Initialize ingestion manager + try: + print("Initializing ingestion manager...") + ingestion_manager = IngestionManager() + print("โœ… Ingestion manager initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize ingestion manager: {e}") + ingestion_manager = None + + # Initialize embedding manager + try: + print("Initializing embedding manager...") + embedding_manager = EmbeddingManager() + print("โœ… Embedding manager initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize embedding manager: {e}") + print(traceback.format_exc()) + embedding_manager = None + + # Initialize vector store + try: + print("Initializing vector store...") + vector_store = VectorStore( + persist_directory=str(VECTOR_DB_DIR), + collection_name=CHROMA_CONFIG['collection_name'] + ) + print("โœ… Vector store initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize vector store: {e}") + print(traceback.format_exc()) + vector_store = None + + # Initialize query processor (depends on embedding and vector store) + if embedding_manager and vector_store: + try: + print("Initializing query processor...") + query_processor = QueryProcessor( + embedding_manager, + vector_store, + { + 'similarity_threshold': 0.5, + 'max_results': 10, + 'enable_cross_modal': True + } + ) + print("โœ… Query processor initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize query processor: {e}") + print(traceback.format_exc()) + query_processor = None + else: + print("โš ๏ธ Skipping query processor (missing dependencies)") + query_processor = None + + # Initialize LLM generator + try: + print("Initializing LLM generator...") + llm_generator = LMStudioGenerator(LM_STUDIO_CONFIG) + print("โœ… LLM generator initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize LLM generator: {e}") + print(traceback.format_exc()) + llm_generator = None + + # Initialize citation generator + try: + print("Initializing citation generator...") + citation_generator = CitationGenerator() + print("โœ… Citation generator initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize citation generator: {e}") + print(traceback.format_exc()) + citation_generator = None + + # Initialize feedback system + try: + print("Initializing feedback system...") + feedback_system = FeedbackSystem() + print("โœ… Feedback system initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize feedback system: {e}") + print(traceback.format_exc()) + feedback_system = None + + # Initialize knowledge graph manager + try: + print("Initializing knowledge graph manager...") + kg_manager = KnowledgeGraphManager() + print("โœ… Knowledge graph manager initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize knowledge graph manager: {e}") + print(traceback.format_exc()) + kg_manager = None + + # Initialize metrics collector + try: + print("Initializing metrics collector...") + metrics_collector = MetricsCollector() + print("โœ… Metrics collector initialized") + except Exception as e: + print(f"โš ๏ธ Failed to initialize metrics collector: {e}") + print(traceback.format_exc()) + metrics_collector = None + + # Initialize speech-to-text processor (optional) + try: + print("Initializing speech-to-text processor...") + stt_processor = SpeechToTextProcessor() + print("โœ… Speech-to-text processor initialized") + except Exception as e: + print(f"โš ๏ธ Speech-to-text processor not available: {e}") + stt_processor = None + + # Summary + initialized = sum([ + ingestion_manager is not None, + embedding_manager is not None, + vector_store is not None, + query_processor is not None, + llm_generator is not None, + citation_generator is not None, + feedback_system is not None, + kg_manager is not None, + ]) + + print(f"\nโœ… Backend API initialized ({initialized}/8 core components)") + print("๐Ÿ“ API documentation available at: http://localhost:8000/docs") + print("๐Ÿ” OpenAPI schema available at: http://localhost:8000/openapi.json") + + +@app.on_event("shutdown") +async def shutdown_event(): + """Cleanup on shutdown""" + global embedding_manager, vector_store + + try: + if embedding_manager: + embedding_manager.save_cache() + if vector_store: + # Vector store auto-persists + pass + except Exception as e: + print(f"Warning: Error during shutdown: {e}") + + +# ==================== Request/Response Models ==================== + +class QueryRequest(BaseModel): + query: str + query_type: str = "text" # text, image, multimodal + k: int = 10 + similarity_threshold: float = 0.5 + filters: Optional[Dict[str, Any]] = None + generate_response: bool = True + + +class QueryResponse(BaseModel): + query_id: str + query: str + response_text: Optional[str] = None + results: List[Dict[str, Any]] = [] + citations: List[Dict[str, Any]] = [] + processing_time: float + total_results: int + model_used: Optional[str] = None + + class Config: + json_schema_extra = { + "example": { + "query_id": "query_123", + "query": "What is RAG?", + "response_text": "RAG is...", + "results": [], + "citations": [], + "processing_time": 1.5, + "total_results": 0, + "model_used": "gemma-3n" + } + } + + +class FileUploadResponse(BaseModel): + file_id: str + filename: str + file_type: str + status: str + processing_time: float + message: str + + class Config: + json_schema_extra = { + "example": { + "file_id": "doc_123", + "filename": "example.pdf", + "file_type": "pdf", + "status": "success", + "processing_time": 2.5, + "message": "File processed and indexed successfully" + } + } + + +class FeedbackRequest(BaseModel): + query: str + response: str + rating: int = Field(ge=1, le=5) + comments: Optional[str] = None + query_metadata: Optional[Dict[str, Any]] = None + + +class ConfigUpdateRequest(BaseModel): + lm_studio_url: Optional[str] = None + similarity_threshold: Optional[float] = None + max_results: Optional[int] = None + model_preference: Optional[str] = None # "gemma" or "qwen" + + +# ==================== Health Check ==================== + +@app.get("/api/health") +async def health_check(): + """Health check endpoint""" + return { + "status": "healthy", + "timestamp": datetime.utcnow().isoformat(), + "components": { + "ingestion": ingestion_manager is not None, + "embedding": embedding_manager is not None, + "vector_store": vector_store is not None, + "query_processor": query_processor is not None, + "llm_generator": llm_generator is not None, + "citation_generator": citation_generator is not None, + "feedback_system": feedback_system is not None, + "kg_manager": kg_manager is not None, + } + } + + +@app.get("/api/test") +async def test_endpoint(): + """Simple test endpoint to verify API connectivity""" + return { + "message": "API is working", + "timestamp": datetime.utcnow().isoformat(), + "status": "ok" + } + + +# ==================== File Upload ==================== + +@app.post("/api/upload", response_model=List[FileUploadResponse]) +async def upload_files( + files: List[UploadFile] = File(...) +): + """Upload and process files""" + if not ingestion_manager or not embedding_manager or not vector_store: + raise HTTPException(status_code=503, detail="Backend components not initialized") + + results = [] + temp_dir = tempfile.mkdtemp() + + try: + # Process files sequentially to avoid overwhelming the system + for idx, file in enumerate(files): + start_time = datetime.now() + + # Validate file + file_ext = Path(file.filename).suffix.lower() + max_size = PROCESSING_CONFIG.get('max_file_size_mb', 100) * 1024 * 1024 + + # Save uploaded file temporarily + temp_path = Path(temp_dir) / file.filename + with open(temp_path, "wb") as f: + content = await file.read() + if len(content) > max_size: + results.append(FileUploadResponse( + file_id="", + filename=file.filename, + file_type=file_ext, + status="error", + processing_time=0, + message=f"File too large (max {max_size / 1024 / 1024}MB)" + )) + continue + f.write(content) + + # Process file + try: + print(f"Processing file {idx + 1}/{len(files)}: {file.filename}") + processed_doc = ingestion_manager.process_file(temp_path) + + if processed_doc: + print(f"File {file.filename} processed, generating embeddings...") + # Generate embeddings + try: + if processed_doc.get('file_type') in ['pdf', 'docx', 'doc', 'txt']: + content_text = processed_doc.get('content_preview', '') + if content_text: + embedding = embedding_manager.embed_text(content_text)[0] + processed_doc['embedding'] = embedding + elif processed_doc.get('file_type') in ['jpg', 'jpeg', 'png', 'bmp', 'tiff', 'webp']: + embedding = embedding_manager.embed_image(str(temp_path))[0] + processed_doc['embedding'] = embedding + except Exception as embed_error: + print(f"Embedding generation failed for {file.filename}: {embed_error}") + # Continue without embedding - file is still processed + + # Add to vector store + try: + vector_store.add_document(processed_doc) + print(f"File {file.filename} added to vector store") + except Exception as store_error: + print(f"Vector store error for {file.filename}: {store_error}") + # Continue - file processing succeeded even if storage failed + + processing_time = (datetime.now() - start_time).total_seconds() + + results.append(FileUploadResponse( + file_id=processed_doc.get('document_id', ''), + filename=file.filename, + file_type=processed_doc.get('file_type', 'unknown'), + status="success", + processing_time=processing_time, + message="File processed and indexed successfully" + )) + print(f"Successfully processed {file.filename} in {processing_time:.2f}s") + else: + print(f"Failed to process {file.filename}: ingestion_manager returned None") + results.append(FileUploadResponse( + file_id="", + filename=file.filename, + file_type=file_ext, + status="error", + processing_time=0, + message="Failed to process file - ingestion manager returned no result" + )) + + except Exception as e: + import traceback + error_trace = traceback.format_exc() + print(f"Error processing {file.filename}: {error_trace}") + results.append(FileUploadResponse( + file_id="", + filename=file.filename, + file_type=file_ext, + status="error", + processing_time=0, + message=f"Processing error: {str(e)}" + )) + + finally: + # Cleanup temp directory + shutil.rmtree(temp_dir, ignore_errors=True) + + return results + + +@app.get("/api/files") +async def list_files(): + """List all uploaded files""" + if not vector_store: + raise HTTPException(status_code=503, detail="Vector store not initialized") + + # Get all documents from vector store + # This is a simplified version - in production, maintain a separate file registry + return {"files": [], "total": 0} + + +@app.delete("/api/files/{file_id}") +async def delete_file(file_id: str): + """Delete a file from the system""" + if not vector_store: + raise HTTPException(status_code=503, detail="Vector store not initialized") + + # Delete from vector store + # In production, implement proper deletion + return {"status": "deleted", "file_id": file_id} + + +# ==================== Query Processing ==================== + +@app.post("/api/query", response_model=QueryResponse) +async def process_query(request: QueryRequest): + """Process a multimodal query""" + if not query_processor or not llm_generator or not citation_generator: + raise HTTPException(status_code=503, detail="Query components not initialized") + + start_time = datetime.now() + query_id = f"query_{datetime.now().timestamp()}" + + try: + # Process query based on type + if request.query_type == "text": + query_result = query_processor.process_text_query( + request.query, + filters=request.filters, + k=request.k + ) + elif request.query_type == "image": + # Image queries would need image data in request + raise HTTPException(status_code=400, detail="Image queries not yet supported via JSON") + else: + raise HTTPException(status_code=400, detail=f"Unsupported query type: {request.query_type}") + + # Generate response if requested + response_text = None + model_used = None + if request.generate_response and llm_generator: + # Prepare context from results + context = [ + { + 'content': r.get('content', ''), + 'content_preview': r.get('content_preview', ''), + 'metadata': r.get('metadata', {}) + } + for r in query_result.results + ] + + # Generate grounded response + generated = llm_generator.generate_grounded_response( + request.query, + context + ) + response_text = generated.response_text + model_used = generated.model_used + + # Generate citations + citations = [] + if response_text and citation_generator: + citations_data = citation_generator.generate_citations( + response_text, + query_result.results + ) + citations = [ + { + 'citation_id': c.citation_id, + 'source_document': c.source_document, + 'source_type': c.source_type, + 'content_snippet': c.content_snippet, + 'confidence_score': c.confidence_score, + 'file_path': c.file_path, + 'page_number': c.page_number + } + for c in citations_data + ] + + processing_time = (datetime.now() - start_time).total_seconds() + + return QueryResponse( + query_id=query_id, + query=request.query, + response_text=response_text, + results=[ + { + 'document_id': r.get('document_id', ''), + 'similarity_score': r.get('similarity_score', 0), + 'content_preview': r.get('content_preview', ''), + 'file_path': r.get('file_path', ''), + 'file_type': r.get('file_type', ''), + 'metadata': r.get('metadata', {}) + } + for r in query_result.results + ], + citations=citations, + processing_time=processing_time, + total_results=len(query_result.results), + model_used=model_used + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Query processing error: {str(e)}") + + +@app.post("/api/query/voice") +async def process_voice_query(audio_file: UploadFile = File(...)): + """Process voice query (speech-to-text then query)""" + if not stt_processor: + raise HTTPException(status_code=503, detail="Speech-to-text not available") + + # Save audio temporarily + temp_path = Path(tempfile.mktemp(suffix=".wav")) + try: + with open(temp_path, "wb") as f: + content = await audio_file.read() + f.write(content) + + # Transcribe + transcription = stt_processor.transcribe_audio(str(temp_path)) + + # Process as text query + request = QueryRequest( + query=transcription, + query_type="text", + generate_response=True + ) + + return await process_query(request) + + finally: + if temp_path.exists(): + temp_path.unlink() + + +@app.get("/api/query/history") +async def get_query_history(limit: int = 20): + """Get query history""" + # In production, maintain query history in database + return {"queries": [], "total": 0} + + +@app.get("/api/query/suggestions") +async def get_query_suggestions(partial: str, limit: int = 5): + """Get query auto-complete suggestions""" + if not query_processor: + return {"suggestions": []} + + suggestions = query_processor.get_query_suggestions(partial, limit) + return {"suggestions": suggestions} + + +# ==================== Analytics ==================== + +@app.get("/api/analytics/metrics") +async def get_metrics(time_range_hours: int = 24): + """Get system performance metrics""" + if not metrics_collector: + return { + "metrics": {}, + "time_range_hours": time_range_hours, + "timestamp": datetime.utcnow().isoformat() + } + + try: + # MetricsCollector has generate_metrics_report method + # For now, return basic structure - can be enhanced later + if hasattr(metrics_collector, 'generate_metrics_report'): + # Convert days to hours for the report + days = max(1, time_range_hours // 24) + metrics = metrics_collector.generate_metrics_report(days=days) + elif hasattr(metrics_collector, '_get_aggregated_query_metrics'): + metrics = { + "query_metrics": metrics_collector._get_aggregated_query_metrics(), + "benchmark_metrics": metrics_collector._get_aggregated_benchmark_metrics() if hasattr(metrics_collector, '_get_aggregated_benchmark_metrics') else {} + } + else: + metrics = {} + + return { + "metrics": metrics if isinstance(metrics, dict) else {"data": metrics}, + "time_range_hours": time_range_hours, + "timestamp": datetime.utcnow().isoformat() + } + except Exception as e: + return { + "metrics": {}, + "time_range_hours": time_range_hours, + "timestamp": datetime.utcnow().isoformat(), + "error": str(e) + } + + +@app.get("/api/analytics/usage") +async def get_usage_stats(time_range_hours: int = 24): + """Get usage statistics""" + # In production, aggregate from query history and feedback + return { + "queries_per_day": 0, + "file_uploads": 0, + "popular_queries": [], + "time_range_hours": time_range_hours + } + + +@app.get("/api/analytics/security") +async def get_security_events(limit: int = 50): + """Get security events and anomalies""" + if not kg_manager: + return {"events": [], "anomalies": [], "total": 0} + + try: + # Get anomalies from knowledge graph + anomalies = kg_manager.detect_anomalies() + + # Ensure anomalies is a list + if not isinstance(anomalies, list): + anomalies = [] + + return { + "events": [], + "anomalies": [ + { + "anomaly_id": a.get('anomaly_id', '') if isinstance(a, dict) else str(a), + "type": a.get('type', '') if isinstance(a, dict) else 'unknown', + "severity": a.get('severity', '') if isinstance(a, dict) else 'low', + "description": a.get('description', '') if isinstance(a, dict) else str(a), + "timestamp": a.get('timestamp', '') if isinstance(a, dict) else '' + } + for a in anomalies[:limit] + ], + "total": len(anomalies) + } + except Exception as e: + print(f"Error getting security events: {e}") + import traceback + traceback.print_exc() + return {"events": [], "anomalies": [], "total": 0, "error": str(e)} + + +@app.get("/api/knowledge-graph") +async def get_knowledge_graph(): + """Get knowledge graph data for visualization""" + if not kg_manager: + return {"nodes": [], "edges": []} + + stats = kg_manager.get_graph_stats() + graph_data = kg_manager.export_graph_for_visualization() + + return { + "nodes": graph_data.get('nodes', []), + "edges": graph_data.get('edges', []), + "stats": stats + } + + +# ==================== Feedback ==================== + +@app.post("/api/feedback") +async def submit_feedback(feedback: FeedbackRequest): + """Submit user feedback""" + if not feedback_system: + raise HTTPException(status_code=503, detail="Feedback system not initialized") + + try: + feedback_system.collect_feedback( + query=feedback.query, + response=feedback.response, + rating=feedback.rating, + comments=feedback.comments, + query_metadata=feedback.query_metadata or {} + ) + + return {"status": "success", "message": "Feedback submitted successfully"} + except Exception as e: + raise HTTPException(status_code=500, detail=f"Failed to submit feedback: {str(e)}") + + +@app.get("/api/feedback/history") +async def get_feedback_history(limit: int = 20): + """Get feedback history""" + if not feedback_system: + return {"feedback": [], "total": 0} + + # Get recent feedback + feedback_data = feedback_system.get_recent_feedback(limit) + + return { + "feedback": [ + { + "feedback_id": f.feedback_id, + "query": f.query, + "rating": f.rating, + "comments": f.comments, + "timestamp": f.timestamp.isoformat() + } + for f in feedback_data + ], + "total": len(feedback_data) + } + + +@app.get("/api/feedback/analytics") +async def get_feedback_analytics(): + """Get feedback analytics""" + if not feedback_system: + return {"analytics": {}} + + metrics = feedback_system.get_feedback_metrics() + return {"analytics": metrics} + + +# ==================== Configuration ==================== + +@app.get("/api/config") +async def get_config(): + """Get current configuration""" + return { + "lm_studio_url": LM_STUDIO_CONFIG.get('base_url', 'http://localhost:1234/v1'), + "similarity_threshold": 0.5, + "max_results": 10, + "model_preference": "auto", + "supported_formats": ingestion_manager.get_supported_formats() if ingestion_manager else [] + } + + +@app.put("/api/config") +async def update_config(config: ConfigUpdateRequest): + """Update configuration""" + # In production, persist configuration changes + return { + "status": "success", + "message": "Configuration updated", + "config": config.dict(exclude_unset=True) + } + + +@app.post("/api/config/validate") +async def validate_config(config: ConfigUpdateRequest): + """Validate configuration before applying""" + errors = [] + + if config.lm_studio_url: + # Validate LM Studio URL + try: + import requests + response = requests.get(f"{config.lm_studio_url}/models", timeout=5) + if response.status_code != 200: + errors.append("LM Studio URL is not accessible") + except Exception as e: + errors.append(f"LM Studio URL validation failed: {str(e)}") + + if config.similarity_threshold is not None: + if not 0.0 <= config.similarity_threshold <= 1.0: + errors.append("Similarity threshold must be between 0.0 and 1.0") + + if config.max_results is not None: + if config.max_results < 1 or config.max_results > 100: + errors.append("Max results must be between 1 and 100") + + return { + "valid": len(errors) == 0, + "errors": errors + } + + +# ==================== Audit Logging ==================== + +@app.get("/api/audit/logs") +async def get_audit_logs(limit: int = 50): + """Get audit logs""" + # In production, retrieve from audit log system + return {"logs": [], "total": 0} + + +@app.get("/api/security/events") +async def get_security_events_endpoint(limit: int = 50): + """Get security events""" + # Reuse the analytics security endpoint logic + if not kg_manager: + return {"events": [], "anomalies": [], "total": 0} + + try: + anomalies = kg_manager.detect_anomalies() + if not isinstance(anomalies, list): + anomalies = [] + + return { + "events": [], + "anomalies": [ + { + "anomaly_id": a.get('anomaly_id', '') if isinstance(a, dict) else str(a), + "type": a.get('type', '') if isinstance(a, dict) else 'unknown', + "severity": a.get('severity', '') if isinstance(a, dict) else 'low', + "description": a.get('description', '') if isinstance(a, dict) else str(a), + "timestamp": a.get('timestamp', '') if isinstance(a, dict) else '' + } + for a in anomalies[:limit] + ], + "total": len(anomalies) + } + except Exception as e: + return {"events": [], "anomalies": [], "total": 0, "error": str(e)} + + +@app.get("/api/security/anomalies") +async def get_anomalies_endpoint(limit: int = 50): + """Get anomaly detection data""" + # Same as security events + return await get_security_events_endpoint(limit) + + +# ==================== Main Entry Point ==================== + +if __name__ == "__main__": + uvicorn.run( + "backend.api.main:app", + host="0.0.0.0", + port=8000, + reload=True + ) diff --git a/backend/api/test_openapi.py b/backend/api/test_openapi.py new file mode 100644 index 0000000..0cc9117 --- /dev/null +++ b/backend/api/test_openapi.py @@ -0,0 +1,37 @@ +""" +Test script to check OpenAPI schema generation +Run this to diagnose Swagger UI issues +""" +import sys +from pathlib import Path + +# Add project root to path +project_root = Path(__file__).parent.parent.parent +sys.path.insert(0, str(project_root)) + +try: + print("Importing FastAPI app...") + from backend.api.main import app + print("โœ… App imported successfully") + + print("\nGenerating OpenAPI schema...") + schema = app.openapi() + print("โœ… OpenAPI schema generated successfully") + + print(f"\nSchema info:") + print(f" - Title: {schema.get('info', {}).get('title')}") + print(f" - Version: {schema.get('info', {}).get('version')}") + print(f" - Paths: {len(schema.get('paths', {}))}") + + print("\nAvailable endpoints:") + for path, methods in schema.get('paths', {}).items(): + for method in methods.keys(): + print(f" {method.upper()} {path}") + + print("\nโœ… All checks passed! Swagger UI should work.") + +except Exception as e: + print(f"โŒ Error: {e}") + import traceback + traceback.print_exc() + sys.exit(1) diff --git a/backend/core/dependencies.py b/backend/core/dependencies.py new file mode 100644 index 0000000..035887e --- /dev/null +++ b/backend/core/dependencies.py @@ -0,0 +1,8 @@ +""" +Core dependencies for backend routers +""" + +from backend.utils.dependencies import get_neurax_service + +# Re-export for compatibility +__all__ = ['get_neurax_service'] diff --git a/backend/core/exceptions.py b/backend/core/exceptions.py new file mode 100644 index 0000000..b1593ac --- /dev/null +++ b/backend/core/exceptions.py @@ -0,0 +1,15 @@ +""" +Backend exceptions +""" + +from enum import Enum + +class ErrorCode(str, Enum): + """Error codes""" + UNKNOWN = "UNKNOWN" + VALIDATION_ERROR = "VALIDATION_ERROR" + NOT_FOUND = "NOT_FOUND" + +class EvaluationError(Exception): + """Evaluation error""" + pass diff --git a/backend/core/settings.py b/backend/core/settings.py new file mode 100644 index 0000000..2a57c1d --- /dev/null +++ b/backend/core/settings.py @@ -0,0 +1,31 @@ +""" +Backend settings and configuration +""" + +from pathlib import Path +from dataclasses import dataclass + +@dataclass +class Settings: + """Application settings""" + paths: 'Paths' + evaluation: 'EvaluationSettings' + +@dataclass +class Paths: + """Path settings""" + data_dir: Path = Path("data") + +@dataclass +class EvaluationSettings: + """Evaluation settings""" + enabled: bool = True + sample_rate: float = 0.1 + track_latency_metrics: bool = True + track_retrieval_metrics: bool = True + track_generation_metrics: bool = True + +settings = Settings( + paths=Paths(), + evaluation=EvaluationSettings() +) diff --git a/backend/services/neurax_service.py b/backend/services/neurax_service.py new file mode 100644 index 0000000..633101c --- /dev/null +++ b/backend/services/neurax_service.py @@ -0,0 +1,47 @@ +""" +NeuraX Service - Main service orchestrator +""" + +import time +from typing import Dict, Any, Optional +from datetime import datetime + +from loguru import logger + + +class NeuraxService: + """Main service that orchestrates all NeuraX components""" + + def __init__(self): + self.start_time = time.time() + self.initialized = False + self.component_status = {} + self.component_errors = [] + + async def get_health_status(self) -> Dict[str, Any]: + """Get comprehensive health status""" + uptime = time.time() - self.start_time + + return { + 'overall_status': 'healthy' if self.initialized else 'initializing', + 'uptime': uptime, + 'component_status': self.component_status, + 'component_errors': self.component_errors, + 'initialized': self.initialized + } + + async def search( + self, + query: str, + k: int = 10, + similarity_threshold: float = 0.5, + generate_response: bool = True + ) -> Dict[str, Any]: + """Perform a search query""" + # This would integrate with query_processor + # For now, return a placeholder + return { + "query": query, + "results": [], + "generated_response": None + } diff --git a/backend/utils/dependencies.py b/backend/utils/dependencies.py new file mode 100644 index 0000000..786dcec --- /dev/null +++ b/backend/utils/dependencies.py @@ -0,0 +1,17 @@ +""" +Dependency injection utilities for FastAPI +""" + +from typing import Optional +from backend.services.neurax_service import NeuraxService + +# Global service instance +_neurax_service: Optional[NeuraxService] = None + + +def get_neurax_service() -> NeuraxService: + """Get or create NeuraxService instance""" + global _neurax_service + if _neurax_service is None: + _neurax_service = NeuraxService() + return _neurax_service diff --git a/neurax-frontend/.env.local.example b/neurax-frontend/.env.local.example new file mode 100644 index 0000000..c9a1b25 --- /dev/null +++ b/neurax-frontend/.env.local.example @@ -0,0 +1,10 @@ +# API Configuration +NEXT_PUBLIC_API_URL=http://localhost:8000 +NEXT_PUBLIC_WS_URL=ws://localhost:8000 +NEXT_PUBLIC_LM_STUDIO_URL=http://localhost:1234 + +# File Upload Configuration +NEXT_PUBLIC_MAX_FILE_SIZE=104857600 + +# Allowed File Types (comma-separated) +NEXT_PUBLIC_ALLOWED_FILE_TYPES=.pdf,.docx,.doc,.txt,.jpg,.jpeg,.png,.bmp,.tiff,.webp,.wav,.mp3,.m4a,.flac,.ogg diff --git a/neurax-frontend/.gitignore b/neurax-frontend/.gitignore new file mode 100644 index 0000000..45c1abc --- /dev/null +++ b/neurax-frontend/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/neurax-frontend/.npmrc b/neurax-frontend/.npmrc new file mode 100644 index 0000000..bb383e8 --- /dev/null +++ b/neurax-frontend/.npmrc @@ -0,0 +1,2 @@ +# npm configuration +legacy-peer-deps=true diff --git a/neurax-frontend/app/(dashboard)/dashboard/analytics/page.tsx b/neurax-frontend/app/(dashboard)/dashboard/analytics/page.tsx new file mode 100644 index 0000000..fa3e20a --- /dev/null +++ b/neurax-frontend/app/(dashboard)/dashboard/analytics/page.tsx @@ -0,0 +1,100 @@ +"use client" + +import { useState, useEffect } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { MetricsChart } from "@/components/analytics/MetricsChart" +import { UsageStats } from "@/components/analytics/UsageStats" +import { SecurityAlerts } from "@/components/analytics/SecurityAlerts" +import { analyticsApi } from "@/lib/api/analytics" +import toast from "react-hot-toast" +import { Loader2 } from "lucide-react" + +export default function AnalyticsPage() { + const [loading, setLoading] = useState(true) + const [metrics, setMetrics] = useState(null) + const [usageStats, setUsageStats] = useState(null) + const [securityEvents, setSecurityEvents] = useState(null) + const [timeRange, setTimeRange] = useState(24) + + useEffect(() => { + loadAnalytics() + }, [timeRange]) + + const loadAnalytics = async () => { + setLoading(true) + try { + const [metricsData, usageData, securityData] = await Promise.all([ + analyticsApi.getMetrics(timeRange), + analyticsApi.getUsageStats(timeRange), + analyticsApi.getSecurityEvents(50), + ]) + setMetrics(metricsData) + setUsageStats(usageData) + setSecurityEvents(securityData) + } catch (error: any) { + toast.error(error.message || "Failed to load analytics") + } finally { + setLoading(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+
+
+

Analytics Dashboard

+

+ System performance metrics and usage statistics +

+
+ +
+ +
+ + + Performance Metrics + + + + + + + + + Usage Statistics + + + + + + + + + Security Alerts + + + + + +
+
+ ) +} diff --git a/neurax-frontend/app/(dashboard)/dashboard/documents/page.tsx b/neurax-frontend/app/(dashboard)/dashboard/documents/page.tsx new file mode 100644 index 0000000..43bdda4 --- /dev/null +++ b/neurax-frontend/app/(dashboard)/dashboard/documents/page.tsx @@ -0,0 +1,26 @@ +"use client" + +import { FileUploader } from "@/components/upload/FileUploader" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" + +export default function DocumentsPage() { + return ( +
+
+

Document Management

+

+ Upload and manage your documents for indexing +

+
+ + + + Upload Documents + + + + + +
+ ) +} diff --git a/neurax-frontend/app/(dashboard)/dashboard/page.tsx b/neurax-frontend/app/(dashboard)/dashboard/page.tsx new file mode 100644 index 0000000..d6c63aa --- /dev/null +++ b/neurax-frontend/app/(dashboard)/dashboard/page.tsx @@ -0,0 +1,44 @@ +"use client" + +import { useState } from "react" +import { QueryInput } from "@/components/query/QueryInput" +import { ResultsDisplay } from "@/components/results/ResultsDisplay" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import type { QueryResponse } from "@/lib/types/api" + +export default function DashboardPage() { + const [queryResult, setQueryResult] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + const handleQuery = async (result: QueryResponse) => { + setQueryResult(result) + } + + return ( +
+
+

Query Interface

+

+ Ask questions about your documents using text, voice, or images +

+
+ + + + Enter Your Query + + + + + + + {queryResult && ( + + )} +
+ ) +} diff --git a/neurax-frontend/app/(dashboard)/dashboard/settings/page.tsx b/neurax-frontend/app/(dashboard)/dashboard/settings/page.tsx new file mode 100644 index 0000000..88c2e82 --- /dev/null +++ b/neurax-frontend/app/(dashboard)/dashboard/settings/page.tsx @@ -0,0 +1,183 @@ +"use client" + +import { useState, useEffect } from "react" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { configApi } from "@/lib/api/config" +import toast from "react-hot-toast" +import { Loader2, Save } from "lucide-react" +import type { ConfigResponse } from "@/lib/types/api" + +export default function SettingsPage() { + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [config, setConfig] = useState>({ + lm_studio_url: "", + similarity_threshold: 0.5, + max_results: 10, + model_preference: "auto", + }) + + useEffect(() => { + loadConfig() + }, []) + + const loadConfig = async () => { + try { + const data = await configApi.getConfig() + setConfig(data) + } catch (error: any) { + toast.error(error.message || "Failed to load configuration") + } finally { + setLoading(false) + } + } + + const handleSave = async () => { + setSaving(true) + try { + // Validate first + const validation = await configApi.validateConfig(config) + if (!validation.valid) { + toast.error(`Validation failed: ${validation.errors.join(", ")}`) + return + } + + await configApi.updateConfig(config) + toast.success("Configuration saved successfully") + } catch (error: any) { + toast.error(error.message || "Failed to save configuration") + } finally { + setSaving(false) + } + } + + if (loading) { + return ( +
+ +
+ ) + } + + return ( +
+
+

Settings

+

+ Configure system settings and preferences +

+
+ + + + LM Studio Configuration + + Configure connection to LM Studio server + + + +
+ + + setConfig({ ...config, lm_studio_url: e.target.value }) + } + placeholder="http://localhost:1234/v1" + /> +
+
+
+ + + + Search Configuration + + Configure search and retrieval parameters + + + +
+ + + setConfig({ + ...config, + similarity_threshold: parseFloat(e.target.value), + }) + } + /> +

+ Minimum similarity score for results (0.0 - 1.0) +

+
+ +
+ + + setConfig({ + ...config, + max_results: parseInt(e.target.value), + }) + } + /> +

+ Maximum number of results to return (1 - 100) +

+
+ +
+ + +

+ Preferred model for generation +

+
+
+
+ +
+ +
+
+ ) +} diff --git a/neurax-frontend/app/(dashboard)/layout.tsx b/neurax-frontend/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..650d6e2 --- /dev/null +++ b/neurax-frontend/app/(dashboard)/layout.tsx @@ -0,0 +1,14 @@ +import { Header } from "@/components/common/Header" + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( +
+
+
{children}
+
+ ) +} diff --git a/neurax-frontend/app/globals.css b/neurax-frontend/app/globals.css new file mode 100644 index 0000000..00b08e3 --- /dev/null +++ b/neurax-frontend/app/globals.css @@ -0,0 +1,59 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 221.2 83.2% 53.3%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 221.2 83.2% 53.3%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 217.2 91.2% 59.8%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 224.3 76.3% 48%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/neurax-frontend/app/layout.tsx b/neurax-frontend/app/layout.tsx new file mode 100644 index 0000000..a2204c5 --- /dev/null +++ b/neurax-frontend/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; +import { Toaster } from "react-hot-toast"; +import { ThemeProvider } from "@/components/providers/theme-provider"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "NeuraX - Multimodal RAG System", + description: "Production-ready offline multimodal Retrieval-Augmented Generation system", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + + {children} + + + + + ); +} diff --git a/neurax-frontend/app/page.tsx b/neurax-frontend/app/page.tsx new file mode 100644 index 0000000..a74cb27 --- /dev/null +++ b/neurax-frontend/app/page.tsx @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export default function Home() { + redirect("/dashboard"); +} diff --git a/neurax-frontend/components/analytics/MetricsChart.tsx b/neurax-frontend/components/analytics/MetricsChart.tsx new file mode 100644 index 0000000..b1f53e1 --- /dev/null +++ b/neurax-frontend/components/analytics/MetricsChart.tsx @@ -0,0 +1,41 @@ +"use client" + +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts" + +interface MetricsChartProps { + metrics: any +} + +export function MetricsChart({ metrics }: MetricsChartProps) { + if (!metrics) { + return

No metrics data available

+ } + + // Transform metrics data for chart + const chartData = [ + { + name: "Retrieval", + value: metrics.metrics?.retrieval?.mrr || 0, + }, + { + name: "Generation", + value: metrics.metrics?.generation?.grounding_score || 0, + }, + { + name: "Latency", + value: metrics.metrics?.latency?.average_total_latency_ms || 0, + }, + ] + + return ( + + + + + + + + + + ) +} diff --git a/neurax-frontend/components/analytics/SecurityAlerts.tsx b/neurax-frontend/components/analytics/SecurityAlerts.tsx new file mode 100644 index 0000000..6e9356b --- /dev/null +++ b/neurax-frontend/components/analytics/SecurityAlerts.tsx @@ -0,0 +1,64 @@ +"use client" + +import { Badge } from "@/components/ui/badge" +import { AlertTriangle, Shield, Info } from "lucide-react" + +interface SecurityAlertsProps { + events: any +} + +export function SecurityAlerts({ events }: SecurityAlertsProps) { + if (!events || !events.anomalies || events.anomalies.length === 0) { + return ( +
+ + No security alerts +
+ ) + } + + const getSeverityIcon = (severity: string) => { + switch (severity?.toLowerCase()) { + case "high": + return + case "medium": + return + default: + return + } + } + + const getSeverityBadge = (severity: string) => { + switch (severity?.toLowerCase()) { + case "high": + return High + case "medium": + return Medium + default: + return Low + } + } + + return ( +
+ {events.anomalies.map((anomaly: any, index: number) => ( +
+ {getSeverityIcon(anomaly.severity)} +
+
+ {anomaly.type} + {getSeverityBadge(anomaly.severity)} +
+

{anomaly.description}

+

+ {anomaly.timestamp ? new Date(anomaly.timestamp).toLocaleString() : ""} +

+
+
+ ))} +
+ ) +} diff --git a/neurax-frontend/components/analytics/UsageStats.tsx b/neurax-frontend/components/analytics/UsageStats.tsx new file mode 100644 index 0000000..5fdf1aa --- /dev/null +++ b/neurax-frontend/components/analytics/UsageStats.tsx @@ -0,0 +1,36 @@ +"use client" + +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts" + +interface UsageStatsProps { + stats: any +} + +export function UsageStats({ stats }: UsageStatsProps) { + if (!stats) { + return

No usage data available

+ } + + const chartData = [ + { + name: "Queries", + value: stats.queries_per_day || 0, + }, + { + name: "Uploads", + value: stats.file_uploads || 0, + }, + ] + + return ( + + + + + + + + + + ) +} diff --git a/neurax-frontend/components/common/Header.tsx b/neurax-frontend/components/common/Header.tsx new file mode 100644 index 0000000..e7ac2f3 --- /dev/null +++ b/neurax-frontend/components/common/Header.tsx @@ -0,0 +1,73 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { Button } from "@/components/ui/button" +import { Moon, Sun, Menu } from "lucide-react" +import { useTheme } from "next-themes" +import { useState, useEffect } from "react" + +export function Header() { + const pathname = usePathname() + const { theme, setTheme } = useTheme() + const [mounted, setMounted] = useState(false) + + useEffect(() => { + setMounted(true) + }, []) + + const navItems = [ + { href: "/dashboard", label: "Query" }, + { href: "/dashboard/documents", label: "Documents" }, + { href: "/dashboard/analytics", label: "Analytics" }, + { href: "/dashboard/settings", label: "Settings" }, + ] + + return ( +
+
+
+ + NeuraX + + +
+
+
+ {/* Mobile menu button could go here */} +
+ +
+
+
+ ) +} diff --git a/neurax-frontend/components/feedback/FeedbackForm.tsx b/neurax-frontend/components/feedback/FeedbackForm.tsx new file mode 100644 index 0000000..06ccec8 --- /dev/null +++ b/neurax-frontend/components/feedback/FeedbackForm.tsx @@ -0,0 +1,101 @@ +"use client" + +import { useState } from "react" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Star } from "lucide-react" +import { feedbackApi } from "@/lib/api/feedback" +import toast from "react-hot-toast" + +interface FeedbackFormProps { + query: string + response: string + onSubmitted?: () => void +} + +export function FeedbackForm({ query, response, onSubmitted }: FeedbackFormProps) { + const [rating, setRating] = useState(0) + const [comments, setComments] = useState("") + const [submitting, setSubmitting] = useState(false) + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (rating === 0) { + toast.error("Please select a rating") + return + } + + setSubmitting(true) + try { + await feedbackApi.submitFeedback({ + query, + response, + rating, + comments: comments || undefined, + }) + toast.success("Thank you for your feedback!") + setRating(0) + setComments("") + if (onSubmitted) { + onSubmitted() + } + } catch (error: any) { + toast.error(error.message || "Failed to submit feedback") + } finally { + setSubmitting(false) + } + } + + return ( + + + Provide Feedback + + +
+
+ +
+ {[1, 2, 3, 4, 5].map((value) => ( + + ))} +
+
+ +
+ +