File size: 5,816 Bytes
c532e4a
ee933ab
c532e4a
 
 
 
 
 
ee933ab
 
 
c532e4a
ee933ab
 
 
 
 
 
7b5cf62
 
 
 
 
 
 
 
ee933ab
 
c532e4a
ee933ab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c532e4a
e7d4d61
ee933ab
c532e4a
ee933ab
c532e4a
ee933ab
c532e4a
 
ee933ab
c532e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee933ab
c532e4a
ee933ab
c532e4a
ee933ab
 
 
e7d4d61
 
 
 
 
 
 
ee933ab
 
 
c532e4a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee933ab
c532e4a
ee933ab
c532e4a
ee933ab
 
 
 
 
 
 
c532e4a
 
 
 
 
 
 
 
 
 
 
ee933ab
 
c532e4a
 
 
 
 
 
 
 
 
ee933ab
 
 
 
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
"""FastAPI application for the FlakeForge environment server.



Mounts:

- OpenEnv core routes (/reset, /step) for RL training compatibility

- FlakeForge API (/api/*) for the web UI and external integrations

- Static homepage (GET /)

- CORS middleware for frontend development

"""

import sys
import os
import logging
from pathlib import Path

project_root = str(Path(__file__).parents[1])
if project_root not in sys.path:
    sys.path.insert(0, project_root)

# Local dev: load repo `.env` so HUGGING_FACE_TOKEN / HF_TOKEN are available to the API and challenge LLM.
try:
    from dotenv import load_dotenv

    load_dotenv(Path(project_root) / ".env", override=False)
except ImportError:
    pass

try:
    from openenv.core.env_server.http_server import create_app
except Exception as e:
    raise ImportError(
        "openenv is required for the web interface. Install dependencies with '\n    uv sync\n'"
    ) from e

try:
    from ..models import FlakeForgeAction, FlakeForgeObservation
    from .FlakeForge_environment import FlakeForgeEnvironment
except ImportError:
    try:
        from FlakeForge.models import FlakeForgeAction, FlakeForgeObservation
        from FlakeForge.server.FlakeForge_environment import FlakeForgeEnvironment
    except ImportError:
        from models import FlakeForgeAction, FlakeForgeObservation  # type: ignore
        from server.FlakeForge_environment import FlakeForgeEnvironment  # type: ignore


from fastapi.middleware.cors import CORSMiddleware
from fastapi import HTTPException
from fastapi.responses import HTMLResponse
from fastapi.routing import APIRoute

logger = logging.getLogger("flakeforge")

# ── Create the base OpenEnv app ─────────────────────────────────────────────
app = create_app(FlakeForgeEnvironment, FlakeForgeAction, FlakeForgeObservation)

# Ensure our custom homepage wins over OpenEnv's default playground at "/".
app.router.routes = [
    route
    for route in app.router.routes
    if not (
        isinstance(route, APIRoute)
        and route.path == "/"
        and "GET" in route.methods
    )
]

# ── CORS ─────────────────────────────────────────────────────────────────────
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ── Mount the FlakeForge API router ─────────────────────────────────────────
try:
    from .api import router as api_router
except ImportError:
    try:
        from server.api import router as api_router
    except ImportError:
        from FlakeForge.server.api import router as api_router  # type: ignore

app.include_router(api_router)

# ── Homepage ─────────────────────────────────────────────────────────────────

@app.get("/", response_class=HTMLResponse)
def homepage():
    """Serve the FlakeForge marketing / demo UI (Hugging Face App tab at `/`)."""
    html_path = Path(__file__).resolve().parents[1] / "templates" / "index.html"
    if not html_path.is_file():
        raise HTTPException(
            status_code=500,
            detail=f"Missing UI template: {html_path.as_posix()}",
        )
    return html_path.read_text(encoding="utf-8")


# ── Startup / shutdown events ────────────────────────────────────────────────

@app.on_event("startup")
async def on_startup():
    logger.info("FlakeForge server starting...")
    logger.info("  Project root:  %s", project_root)
    logger.info("  API docs:      http://localhost:8000/docs")
    logger.info("  Homepage:      http://localhost:8000/")
    logger.info("  Health check:  http://localhost:8000/api/health")


@app.on_event("shutdown")
async def on_shutdown():
    logger.info("FlakeForge server shutting down.")


# ── CLI entry point ──────────────────────────────────────────────────────────

def main(host: str = "0.0.0.0", port: int = 8000):
    """Entry point for direct execution.



    Usage:

        uv run --project . server

        uv run --project . server --port 8001

        python -m FlakeForge.server.app

    """
    import argparse
    import uvicorn

    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
        datefmt="%H:%M:%S",
    )

    parser = argparse.ArgumentParser(description="FlakeForge Environment Server")
    parser.add_argument("--host", type=str, default=host, help="Host address")
    parser.add_argument("--port", type=int, default=port, help="Port number")
    parser.add_argument("--reload", action="store_true", help="Auto-reload on code changes")
    parser.add_argument("--workers", type=int, default=1, help="Number of workers")
    args, _ = parser.parse_known_args()

    logger.info("Starting FlakeForge server on %s:%d", args.host, args.port)
    uvicorn.run(
        "server.app:app" if args.reload else app,
        host=args.host,
        port=args.port,
        reload=args.reload,
        workers=args.workers,
        log_level="info",
    )


if __name__ == "__main__":
    main()