#!/usr/bin/env python3 """ ResearchMate Setup Script Complete setup and configuration for ResearchMate """ import os import sys import json import shutil import subprocess import logging from pathlib import Path from typing import Dict, List, Optional, Any import configparser import getpass # Setup logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(Path(__file__).parent.parent.parent / 'logs' / 'setup.log'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) class ResearchMateSetup: """Complete setup system for ResearchMate""" def __init__(self, project_root: Optional[Path] = None): self.project_root = project_root or Path(__file__).parent.parent.parent self.config_dir = self.project_root / "config" self.settings_file = self.config_dir / "settings.json" self.env_file = self.project_root / ".env" def print_banner(self): """Print setup banner""" banner = """ ResearchMate Setup System ============================ AI Research Assistant Configuration Version: 2.0.0 """ print(banner) logger.info("Starting ResearchMate setup") def create_config_structure(self): """Create configuration directory structure""" try: logger.info("Creating configuration structure...") # Create directories directories = [ "config", "logs", "uploads", "chroma_db", "chroma_persist", "src/templates", "src/tests" ] for directory in directories: dir_path = self.project_root / directory dir_path.mkdir(parents=True, exist_ok=True) logger.info(f"Created: {directory}") return True except Exception as e: logger.error(f"Failed to create configuration structure: {e}") return False def collect_configuration(self) -> Dict[str, Any]: """Collect configuration from user""" config = {} print("\nConfiguration Setup") print("Please provide the following information:") # API Keys print("\nAPI Configuration:") groq_api_key = getpass.getpass("Enter your Groq API key: ") config["groq_api_key"] = groq_api_key # Server Configuration print("\nServer Configuration:") config["server"] = { "host": input("Host (default: 0.0.0.0): ") or "0.0.0.0", "port": int(input("Port (default: 8000): ") or "8000"), "debug": input("Debug mode (y/n, default: n): ").lower() == 'y' } # Database Configuration print("\nDatabase Configuration:") config["database"] = { "chroma_persist_dir": input("ChromaDB persist directory (default: ./chroma_persist): ") or "./chroma_persist", "collection_name": input("Collection name (default: research_documents): ") or "research_documents" } # AI Model Configuration print("\nAI Model Configuration:") config["ai_model"] = { "model_name": input("Groq model name (default: llama-3.3-70b-versatile): ") or "llama-3.3-70b-versatile", "temperature": float(input("Temperature (default: 0.7): ") or "0.7"), "max_tokens": int(input("Max tokens (default: 4096): ") or "4096") } # Search Configuration print("\nSearch Configuration:") config["search"] = { "max_results": int(input("Max search results (default: 10): ") or "10"), "similarity_threshold": float(input("Similarity threshold (default: 0.7): ") or "0.7") } # Upload Configuration print("\nUpload Configuration:") config["upload"] = { "max_file_size": int(input("Max file size in MB (default: 50): ") or "50") * 1024 * 1024, "allowed_extensions": [".pdf", ".txt", ".md", ".docx"] } return config def save_configuration(self, config: Dict[str, Any]): """Save configuration to settings file""" try: logger.info("Saving configuration...") # Create settings configuration settings_config = { "server": { "host": config['server']['host'], "port": config['server']['port'], "debug": config['server']['debug'], "reload": False, "workers": 1, "log_level": "info" }, "database": { "chroma_persist_dir": config['database']['chroma_persist_dir'], "collection_name": config['database']['collection_name'], "similarity_threshold": 0.7, "max_results": config['search']['max_results'], "embedding_model": "all-MiniLM-L6-v2" }, "ai_model": { "model_name": config['ai_model']['model_name'], "temperature": config['ai_model']['temperature'], "max_tokens": config['ai_model']['max_tokens'], "top_p": 0.9, "frequency_penalty": 0.0, "presence_penalty": 0.0, "timeout": 30 }, "upload": { "max_file_size": config['upload']['max_file_size'], "allowed_extensions": config['upload']['allowed_extensions'], "upload_directory": "./uploads", "temp_directory": "./tmp" }, "search": { "max_results": config['search']['max_results'], "similarity_threshold": config['search']['similarity_threshold'], "enable_reranking": True, "chunk_size": 1000, "chunk_overlap": 200 }, "security": { "cors_origins": ["*"], "cors_methods": ["*"], "cors_headers": ["*"], "rate_limit_enabled": True, "rate_limit_requests": 100, "rate_limit_period": 60 }, "logging": { "level": "INFO", "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", "file_enabled": True, "file_path": "./logs/app.log", "max_file_size": 10485760, "backup_count": 5, "console_enabled": True } } # Save to settings.json settings_file = self.config_dir / "settings.json" with open(settings_file, 'w') as f: json.dump(settings_config, f, indent=2) # Save environment variables env_content = f"""# ResearchMate Environment Variables GROQ_API_KEY={config['groq_api_key']} ENVIRONMENT=production DEBUG={str(config['server']['debug']).lower()} HOST={config['server']['host']} PORT={config['server']['port']} """ with open(self.env_file, 'w') as f: f.write(env_content) logger.info("Configuration saved successfully") logger.info(f"Settings file: {settings_file}") logger.info(f"Environment file: {self.env_file}") return True except Exception as e: logger.error(f"Failed to save configuration: {e}") return False def create_startup_script(self): """Create startup script""" try: logger.info("Creating startup script...") startup_content = """#!/usr/bin/env python3 # ResearchMate Startup Script import sys from pathlib import Path sys.path.append(str(Path(__file__).parent)) from scripts.deploy import ResearchMateDeployer def main(): deployer = ResearchMateDeployer() if deployer.deploy(): deployer.start_server() else: sys.exit(1) if __name__ == "__main__": main() """ startup_script = self.project_root / "start_researchmate.py" with open(startup_script, 'w') as f: f.write(startup_content) # Make executable on Unix systems if os.name != 'nt': os.chmod(startup_script, 0o755) logger.info("Startup script created") return True except Exception as e: logger.error(f"Failed to create startup script: {e}") return False def create_test_files(self): """Create test files structure""" try: logger.info("Creating test files...") # Create test directory structure test_dirs = [ "src/tests", "src/tests/unit", "src/tests/integration", "src/tests/fixtures" ] for test_dir in test_dirs: (self.project_root / test_dir).mkdir(parents=True, exist_ok=True) # Create __init__.py files for test_dir in test_dirs: init_file = self.project_root / test_dir / "__init__.py" init_file.touch() # Create basic test files test_files = { "src/tests/test_basic.py": '''"""Basic tests for ResearchMate""" import pytest import sys from pathlib import Path # Add project root to path sys.path.append(str(Path(__file__).parent.parent.parent)) def test_imports(): """Test that core modules can be imported""" try: from src.components import ResearchAssistant from src.components.config import Config assert True except ImportError as e: pytest.fail(f"Import failed: {e}") def test_config_loading(): """Test configuration loading""" from src.components.config import Config config = Config() assert config is not None ''', "src/tests/conftest.py": '''"""Test configuration""" import pytest import tempfile import shutil from pathlib import Path @pytest.fixture def temp_dir(): """Create temporary directory for tests""" temp_dir = Path(tempfile.mkdtemp()) yield temp_dir shutil.rmtree(temp_dir) @pytest.fixture def sample_config(): """Sample configuration for tests""" return { "server": {"host": "127.0.0.1", "port": 8000}, "database": {"chroma_persist_dir": "./test_chroma"}, "ai_model": {"model_name": "llama-3.3-70b-versatile"} } ''', "src/tests/unit/test_components.py": '''"""Unit tests for components""" import pytest import sys from pathlib import Path # Add project root to path sys.path.append(str(Path(__file__).parent.parent.parent.parent)) def test_research_assistant_init(): """Test ResearchAssistant initialization""" # Add your component tests here assert True def test_config_validation(): """Test configuration validation""" # Add your config tests here assert True ''', "src/tests/integration/test_api.py": '''"""Integration tests for API""" import pytest import sys from pathlib import Path # Add project root to path sys.path.append(str(Path(__file__).parent.parent.parent.parent)) def test_api_endpoints(): """Test API endpoints""" # Add your API tests here assert True ''' } for file_path, content in test_files.items(): full_path = self.project_root / file_path with open(full_path, 'w') as f: f.write(content) logger.info("Test files created") return True except Exception as e: logger.error(f"Failed to create test files: {e}") return False def create_development_tools(self): """Create development tools and scripts""" try: logger.info("Creating development tools...") # Create development requirements dev_requirements = """# Development dependencies pytest==7.4.3 pytest-asyncio==0.21.1 pytest-cov==4.1.0 black==23.11.0 flake8==6.1.0 isort==5.12.0 watchdog==3.0.0 pre-commit==3.5.0 """ with open(self.project_root / "requirements-dev.txt", 'w') as f: f.write(dev_requirements) # Create pre-commit configuration pre_commit_config = """repos: - repo: https://github.com/psf/black rev: 23.11.0 hooks: - id: black language_version: python3 - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: - id: flake8 args: [--max-line-length=88] - repo: https://github.com/pycqa/isort rev: 5.12.0 hooks: - id: isort args: ["--profile", "black"] """ with open(self.project_root / ".pre-commit-config.yaml", 'w') as f: f.write(pre_commit_config) # Create setup.cfg for tool configuration setup_cfg = """[flake8] max-line-length = 88 exclude = .git,__pycache__,venv,build,dist ignore = E203,W503 [isort] profile = black multi_line_output = 3 line_length = 88 [tool:pytest] testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = -v --tb=short """ with open(self.project_root / "setup.cfg", 'w') as f: f.write(setup_cfg) logger.info("Development tools created") return True except Exception as e: logger.error(f"Failed to create development tools: {e}") return False def update_requirements(self): """Update requirements.txt with additional development packages""" try: logger.info("Updating requirements...") # Add development packages to main requirements additional_packages = [ "python-dotenv==1.0.0", "watchdog==3.0.0" ] with open(self.project_root / "requirements.txt", 'r') as f: current_requirements = f.read() # Add packages if not already present for package in additional_packages: if package.split('==')[0] not in current_requirements: current_requirements += f"\n{package}" with open(self.project_root / "requirements.txt", 'w') as f: f.write(current_requirements) logger.info("Requirements updated") return True except Exception as e: logger.error(f"Failed to update requirements: {e}") return False def run_setup(self): """Run complete setup process""" self.print_banner() steps = [ ("Creating configuration structure", self.create_config_structure), ("Updating requirements", self.update_requirements), ("Creating test files", self.create_test_files), ("Creating development tools", self.create_development_tools), ("Creating startup script", self.create_startup_script), ] for step_name, step_func in steps: logger.info(f"Running: {step_name}") if not step_func(): logger.error(f"Failed at step: {step_name}") return False # Collect and save configuration config = self.collect_configuration() if not self.save_configuration(config): return False logger.info("Setup completed successfully!") logger.info("\nNext steps:") logger.info("1. Run: python scripts/deploy.py") logger.info("2. Or run: python start_researchmate.py") logger.info("3. For development: python scripts/dev_server.py") return True def main(): """Main setup function""" import argparse parser = argparse.ArgumentParser(description="ResearchMate Setup System") parser.add_argument("--skip-config", action="store_true", help="Skip configuration collection") args = parser.parse_args() setup = ResearchMateSetup() if setup.run_setup(): logger.info("Setup completed successfully!") else: logger.error("Setup failed!") sys.exit(1) if __name__ == "__main__": main()