File size: 6,967 Bytes
ff3e1be
 
 
 
039e1ea
ff3e1be
 
 
039e1ea
ff3e1be
039e1ea
 
ff3e1be
b4b210e
9852326
ff3e1be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101ad87
 
 
 
 
 
 
ff3e1be
 
 
 
 
1226a86
 
 
 
 
 
ff3e1be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de797a5
ff3e1be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9852326
 
e13f862
54ec9cb
b4b210e
1226a86
 
 
 
4afa792
 
 
 
ff3e1be
039e1ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ff3e1be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""FastAPI application entry point with CORS and lifespan management."""

import logging
from contextlib import asynccontextmanager
from pathlib import Path
from typing import AsyncGenerator

import uvicorn
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles

from app.api.routes import agents, episode, health, memory, openenv, plugins, scrape, sites, tasks, tools
from app.api.routes import settings as settings_routes
from app.config import get_settings
from app.memory.manager import MemoryManager
from app.models.router import SmartModelRouter
from app.tools.registry import MCPToolRegistry
from app.utils.logging import setup_logging

logger = logging.getLogger(__name__)

# Global instances for dependency injection
_memory_manager: MemoryManager | None = None
_model_router: SmartModelRouter | None = None
_tool_registry: MCPToolRegistry | None = None


def get_memory_manager() -> MemoryManager:
    """Get the global memory manager instance."""
    if _memory_manager is None:
        raise RuntimeError("Memory manager not initialized")
    return _memory_manager


def get_model_router() -> SmartModelRouter:
    """Get the global model router instance."""
    if _model_router is None:
        raise RuntimeError("Model router not initialized")
    return _model_router


def get_tool_registry() -> MCPToolRegistry:
    """Get the global tool registry instance."""
    if _tool_registry is None:
        raise RuntimeError("Tool registry not initialized")
    return _tool_registry


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """Manage application lifespan - startup and shutdown events."""
    global _memory_manager, _model_router, _tool_registry

    settings = get_settings()
    logger.info(f"Starting {settings.app_name} v{settings.app_version}")

    # Initialize components
    logger.info("Initializing memory manager...")
    _memory_manager = MemoryManager(settings)
    await _memory_manager.initialize()

    logger.info("Initializing model router...")
    _model_router = SmartModelRouter(
        openai_api_key=settings.openai_api_key,
        anthropic_api_key=settings.anthropic_api_key,
        google_api_key=settings.google_api_key,
        groq_api_key=settings.groq_api_key,
        nvidia_api_key=settings.nvidia_api_key,
    )
    await _model_router.initialize()

    logger.info("Initializing tool registry...")
    _tool_registry = MCPToolRegistry()
    await _tool_registry.initialize()
    
    # Update dependency container
    from app.api import deps
    deps.container.set_memory_manager(_memory_manager)
    deps.container.set_model_router(_model_router)
    deps.container.set_tool_registry(_tool_registry)

    logger.info("Application startup complete")

    yield

    # Shutdown
    logger.info("Shutting down application...")

    if _memory_manager:
        await _memory_manager.shutdown()
    if _model_router:
        await _model_router.shutdown()
    if _tool_registry:
        await _tool_registry.shutdown()

    logger.info("Application shutdown complete")


def create_app() -> FastAPI:
    """Create and configure the FastAPI application."""
    settings = get_settings()
    setup_logging(settings.log_level)

    app = FastAPI(
        title=settings.app_name,
        description="FastAPI-based RL environment for intelligent web scraping",
        version=settings.app_version,
        debug=settings.debug,
        lifespan=lifespan,
        docs_url="/swagger",
        redoc_url="/redoc",
        openapi_url="/openapi.json",
    )

    # Configure CORS
    app.add_middleware(
        CORSMiddleware,
        allow_origins=settings.cors_origins,
        allow_credentials=settings.cors_allow_credentials,
        allow_methods=settings.cors_allow_methods,
        allow_headers=settings.cors_allow_headers,
    )

    # Include routers
    api_prefix = "/api"
    app.include_router(health.router, prefix=api_prefix, tags=["Health"])
    app.include_router(episode.router, prefix=api_prefix, tags=["Episode"])
    app.include_router(tasks.router, prefix=api_prefix, tags=["Tasks"])
    app.include_router(agents.router, prefix=api_prefix, tags=["Agents"])
    app.include_router(tools.router, prefix=api_prefix, tags=["Tools"])
    app.include_router(memory.router, prefix=api_prefix, tags=["Memory"])
    app.include_router(settings_routes.router, prefix=api_prefix, tags=["Settings"])
    app.include_router(plugins.router, prefix=api_prefix, tags=["Plugins"])
    app.include_router(sites.router, prefix=api_prefix, tags=["Sites"])
    app.include_router(scrape.router, prefix=api_prefix, tags=["Scraping"])
    app.include_router(openenv.router, tags=["OpenEnv"])
    
    # Import and include providers router
    from app.api.routes import providers
    app.include_router(providers.router, prefix=api_prefix, tags=["Providers"])
    
    # Import and include WebSocket router
    from app.api.routes import websocket
    app.include_router(websocket.router, tags=["WebSocket"])

    # Serve static files (frontend build)
    static_dir = Path(__file__).parent.parent / "static"
    if static_dir.exists():
        app.mount("/assets", StaticFiles(directory=static_dir / "assets"), name="assets")
        
        @app.get("/", response_class=HTMLResponse)
        async def serve_spa():
            """Serve the main SPA index.html."""
            index_file = static_dir / "index.html"
            if index_file.exists():
                return FileResponse(index_file)
            return HTMLResponse("<h1>ScrapeRL</h1><p>Frontend not built.</p>")
        
        @app.get("/{full_path:path}")
        async def serve_spa_routes(request: Request, full_path: str):
            """Serve SPA routes - return index.html for client-side routing."""
            # Don't serve index.html for API routes
            if full_path.startswith("api/"):
                return {"detail": "Not Found"}
            
            # Check if it's a static file
            static_file = static_dir / full_path
            if static_file.exists() and static_file.is_file():
                return FileResponse(static_file)
            
            # Return index.html for SPA routing
            index_file = static_dir / "index.html"
            if index_file.exists():
                return FileResponse(index_file)
            return HTMLResponse("<h1>ScrapeRL</h1><p>Frontend not built.</p>")

    return app


# Create the application instance
app = create_app()


def run() -> None:
    """Run the application using uvicorn."""
    settings = get_settings()
    uvicorn.run(
        "app.main:app",
        host=settings.host,
        port=settings.port,
        reload=settings.reload,
        workers=settings.workers,
        log_level=settings.log_level.lower(),
    )


if __name__ == "__main__":
    run()