Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import os | |
| from fastapi.responses import HTMLResponse | |
| from openenv.core.env_server.http_server import create_app | |
| from models import CareerPlanningAction, CareerPlanningObservation, CareerPlanningState | |
| from server.career_planning_environment import CareerPlanningEnvironment | |
| app = create_app( | |
| CareerPlanningEnvironment, | |
| CareerPlanningAction, | |
| CareerPlanningObservation, | |
| env_name="career_planning", | |
| max_concurrent_envs=4, | |
| ) | |
| def root() -> HTMLResponse: | |
| return HTMLResponse( | |
| """ | |
| <!doctype html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>AI Career Advisor</title> | |
| <style> | |
| :root { | |
| --bg: #f4f7f1; | |
| --card: #ffffff; | |
| --ink: #172119; | |
| --muted: #5e6c60; | |
| --accent: #1f8f5f; | |
| --accent-dark: #136746; | |
| --line: #d9e5da; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| font-family: "Segoe UI", Arial, sans-serif; | |
| color: var(--ink); | |
| background: | |
| radial-gradient(circle at top left, #dff6df 0, transparent 35%), | |
| linear-gradient(160deg, #f8fbf5 0%, var(--bg) 100%); | |
| } | |
| .wrap { | |
| max-width: 980px; | |
| margin: 0 auto; | |
| padding: 48px 20px 64px; | |
| } | |
| .hero { | |
| background: rgba(255,255,255,0.86); | |
| border: 1px solid var(--line); | |
| border-radius: 24px; | |
| padding: 28px; | |
| box-shadow: 0 18px 50px rgba(16, 37, 23, 0.08); | |
| } | |
| h1 { | |
| margin: 0 0 10px; | |
| font-size: clamp(2rem, 4vw, 3.5rem); | |
| line-height: 1; | |
| } | |
| p { | |
| margin: 0; | |
| color: var(--muted); | |
| font-size: 1.05rem; | |
| line-height: 1.6; | |
| } | |
| .links, .grid { | |
| display: grid; | |
| gap: 16px; | |
| } | |
| .links { | |
| margin-top: 24px; | |
| grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); | |
| } | |
| a.link, button { | |
| display: inline-flex; | |
| justify-content: center; | |
| align-items: center; | |
| text-decoration: none; | |
| border-radius: 14px; | |
| padding: 14px 16px; | |
| border: 0; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| } | |
| a.link.primary, button.primary { | |
| background: var(--accent); | |
| color: white; | |
| } | |
| a.link.secondary { | |
| background: white; | |
| color: var(--ink); | |
| border: 1px solid var(--line); | |
| } | |
| .grid { | |
| margin-top: 28px; | |
| grid-template-columns: 1.2fr 0.8fr; | |
| } | |
| .card { | |
| background: var(--card); | |
| border: 1px solid var(--line); | |
| border-radius: 20px; | |
| padding: 22px; | |
| } | |
| .card h2 { | |
| margin: 0 0 14px; | |
| font-size: 1.15rem; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| } | |
| textarea, pre { | |
| width: 100%; | |
| border-radius: 14px; | |
| border: 1px solid var(--line); | |
| background: #fbfdf9; | |
| } | |
| textarea { | |
| min-height: 110px; | |
| padding: 14px; | |
| resize: vertical; | |
| font: inherit; | |
| } | |
| pre { | |
| min-height: 260px; | |
| padding: 14px; | |
| overflow: auto; | |
| margin: 0; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .hint { | |
| margin-top: 10px; | |
| font-size: 0.92rem; | |
| color: var(--muted); | |
| } | |
| ul { | |
| margin: 0; | |
| padding-left: 18px; | |
| color: var(--muted); | |
| } | |
| li + li { margin-top: 8px; } | |
| @media (max-width: 800px) { | |
| .grid { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="wrap"> | |
| <section class="hero"> | |
| <h1>AI Career Advisor</h1> | |
| <p>Test the OpenEnv deployment directly from the browser. The API remains fully compatible with the automated checker, and this page gives you a simple way to explore it manually.</p> | |
| <div class="links"> | |
| <a class="link primary" href="/docs">Open API Docs</a> | |
| <a class="link secondary" href="/health">Health Check</a> | |
| <a class="link secondary" href="/schema">Schema</a> | |
| <a class="link secondary" href="/openapi.json">OpenAPI JSON</a> | |
| </div> | |
| </section> | |
| <section class="grid"> | |
| <div class="card"> | |
| <h2>Run Reset</h2> | |
| <label for="payload">Reset payload</label> | |
| <textarea id="payload">{}</textarea> | |
| <div style="margin-top: 14px;"> | |
| <button class="primary" onclick="runReset()">POST /reset</button> | |
| </div> | |
| <div class="hint">Example: {"user_skills": ["python", "sql"]}</div> | |
| </div> | |
| <div class="card"> | |
| <h2>About This Space</h2> | |
| <ul> | |
| <li>`POST /reset` returns `observation`, `reward`, and `done`.</li> | |
| <li>`POST /step` accepts actions like `choose_career`, `learn_skill`, and `work`.</li> | |
| <li>`GET /state` returns the current episode state.</li> | |
| <li>The automated Phase 1 checks target these API endpoints.</li> | |
| </ul> | |
| </div> | |
| </section> | |
| <section class="card" style="margin-top: 16px;"> | |
| <h2>Response</h2> | |
| <pre id="result">Click "POST /reset" to test the deployed environment.</pre> | |
| </section> | |
| </div> | |
| <script> | |
| async function runReset() { | |
| const result = document.getElementById("result"); | |
| const payload = document.getElementById("payload").value || "{}"; | |
| result.textContent = "Loading..."; | |
| try { | |
| const response = await fetch("/reset", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: payload | |
| }); | |
| const data = await response.json(); | |
| result.textContent = JSON.stringify(data, null, 2); | |
| } catch (error) { | |
| result.textContent = String(error); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| ) | |
| def main(host: str = "0.0.0.0", port: int | None = None) -> None: | |
| import uvicorn | |
| uvicorn.run(app, host=host, port=port or int(os.environ.get("PORT", "7860"))) | |
| if __name__ == "__main__": | |
| main() | |