Commit ·
70b7446
1
Parent(s): 21996c1
change approach to docker
Browse files- Dockerfile +10 -2
- README.md +5 -5
- app.py +163 -9
- requirements.txt +3 -1
Dockerfile
CHANGED
|
@@ -1,2 +1,10 @@
|
|
| 1 |
-
FROM
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt .
|
| 6 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1"]
|
README.md
CHANGED
|
@@ -2,9 +2,8 @@
|
|
| 2 |
title: AlpaSim Challenge 2026
|
| 3 |
colorFrom: green
|
| 4 |
colorTo: blue
|
| 5 |
-
sdk:
|
| 6 |
-
|
| 7 |
-
app_file: app.py
|
| 8 |
hf_oauth: true
|
| 9 |
hf_oauth_scopes:
|
| 10 |
- email
|
|
@@ -12,7 +11,7 @@ hf_oauth_scopes:
|
|
| 12 |
|
| 13 |
# AlpaSim Challenge 2026
|
| 14 |
|
| 15 |
-
This
|
| 16 |
|
| 17 |
- team registration
|
| 18 |
- signed-in participant registration status
|
|
@@ -63,4 +62,5 @@ Each track will award two NVIDIA DGX prizes:
|
|
| 63 |
- PAI-AV track: two NVIDIA DGX prizes for the top two teams.
|
| 64 |
- nuPlan track: two NVIDIA DGX prizes for the top two teams.
|
| 65 |
|
| 66 |
-
The app
|
|
|
|
|
|
| 2 |
title: AlpaSim Challenge 2026
|
| 3 |
colorFrom: green
|
| 4 |
colorTo: blue
|
| 5 |
+
sdk: docker
|
| 6 |
+
app_port: 7860
|
|
|
|
| 7 |
hf_oauth: true
|
| 8 |
hf_oauth_scopes:
|
| 9 |
- email
|
|
|
|
| 11 |
|
| 12 |
# AlpaSim Challenge 2026
|
| 13 |
|
| 14 |
+
This Docker Space serves a static landing page with an embedded Gradio app for:
|
| 15 |
|
| 16 |
- team registration
|
| 17 |
- signed-in participant registration status
|
|
|
|
| 62 |
- PAI-AV track: two NVIDIA DGX prizes for the top two teams.
|
| 63 |
- nuPlan track: two NVIDIA DGX prizes for the top two teams.
|
| 64 |
|
| 65 |
+
The app serves the landing page from FastAPI at `/` and mounts Gradio at
|
| 66 |
+
`/gradio`.
|
app.py
CHANGED
|
@@ -8,6 +8,9 @@ from pathlib import Path
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
|
| 13 |
API_BASE_URL = os.environ.get(
|
|
@@ -39,11 +42,12 @@ BANNER_HTML = f"""
|
|
| 39 |
<a href="#timeline">Timeline</a>
|
| 40 |
<a href="#prizes">Prizes</a>
|
| 41 |
<a href="#organizers">Organizers</a>
|
|
|
|
| 42 |
</div>
|
| 43 |
</div>
|
| 44 |
<header class="alpasim-hero">
|
| 45 |
<video autoplay muted loop playsinline preload="auto" aria-label="AlpaSim E2E Closed Loop Challenge 2026 banner">
|
| 46 |
-
<source src="/
|
| 47 |
</video>
|
| 48 |
<div class="hero-overlay">
|
| 49 |
<div class="hero-overlay-inner">
|
|
@@ -199,7 +203,28 @@ CHALLENGE_OVERVIEW_HTML = """
|
|
| 199 |
</section>
|
| 200 |
</main>
|
| 201 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
BANNER_CSS = """
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
:root,
|
| 204 |
.gradio-container {
|
| 205 |
--bg: #ffffff;
|
|
@@ -238,7 +263,7 @@ BANNER_CSS = """
|
|
| 238 |
justify-content: space-between;
|
| 239 |
gap: 20px;
|
| 240 |
padding: 12px 24px;
|
| 241 |
-
margin:
|
| 242 |
background: rgba(255, 255, 255, 0.92);
|
| 243 |
backdrop-filter: blur(8px);
|
| 244 |
border-bottom: 1px solid var(--border);
|
|
@@ -272,7 +297,7 @@ BANNER_CSS = """
|
|
| 272 |
.alpasim-hero {
|
| 273 |
position: relative;
|
| 274 |
overflow: hidden;
|
| 275 |
-
margin: 0
|
| 276 |
background: #000000;
|
| 277 |
border-bottom: 1px solid var(--border);
|
| 278 |
}
|
|
@@ -331,6 +356,7 @@ BANNER_CSS = """
|
|
| 331 |
.challenge-info section {
|
| 332 |
padding: 48px 0;
|
| 333 |
border-bottom: 1px solid var(--border);
|
|
|
|
| 334 |
}
|
| 335 |
|
| 336 |
.challenge-info section:last-child {
|
|
@@ -496,6 +522,37 @@ BANNER_CSS = """
|
|
| 496 |
margin: 0 auto;
|
| 497 |
}
|
| 498 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
@media (max-width: 720px) {
|
| 500 |
.topline {
|
| 501 |
align-items: flex-start;
|
|
@@ -530,11 +587,87 @@ BANNER_CSS = """
|
|
| 530 |
padding: 0 18px;
|
| 531 |
}
|
| 532 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
.identity-strip {
|
| 534 |
padding: 0 18px 18px;
|
| 535 |
}
|
| 536 |
}
|
| 537 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 538 |
|
| 539 |
|
| 540 |
def split_multivalue(value: str) -> list[str]:
|
|
@@ -563,7 +696,7 @@ def current_identity(profile: gr.OAuthProfile | None) -> str:
|
|
| 563 |
"To register a team, connect this Space to your Hugging Face account. "
|
| 564 |
"If you are already signed in on huggingface.co, this just authorizes "
|
| 565 |
"the Space to read your Hugging Face profile. "
|
| 566 |
-
"[Connect Hugging Face account](/login/huggingface?_target_url=/)"
|
| 567 |
)
|
| 568 |
|
| 569 |
username = profile_value(profile, "preferred_username", "name")
|
|
@@ -574,7 +707,7 @@ def current_identity(profile: gr.OAuthProfile | None) -> str:
|
|
| 574 |
parts.append(f"Stable HF user ID: `{user_id}`.")
|
| 575 |
if email:
|
| 576 |
parts.append(f"HF email: `{email}`.")
|
| 577 |
-
parts.append("[Disconnect](/logout?_target_url=/).")
|
| 578 |
return " ".join(parts)
|
| 579 |
|
| 580 |
|
|
@@ -1188,11 +1321,9 @@ def add_oauth_route_marker() -> None:
|
|
| 1188 |
with gr.Blocks(
|
| 1189 |
title="AlpaSim E2E Closed Loop Challenge 2026",
|
| 1190 |
theme=APP_THEME,
|
| 1191 |
-
css=
|
| 1192 |
) as demo:
|
| 1193 |
add_oauth_route_marker()
|
| 1194 |
-
gr.HTML(BANNER_HTML)
|
| 1195 |
-
gr.HTML(CHALLENGE_OVERVIEW_HTML)
|
| 1196 |
with gr.Group(elem_classes=["identity-strip"]):
|
| 1197 |
identity = gr.Markdown()
|
| 1198 |
|
|
@@ -1449,5 +1580,28 @@ with gr.Blocks(
|
|
| 1449 |
],
|
| 1450 |
)
|
| 1451 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1452 |
if __name__ == "__main__":
|
| 1453 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
import gradio as gr
|
| 11 |
+
from fastapi import FastAPI
|
| 12 |
+
from fastapi.responses import HTMLResponse
|
| 13 |
+
from fastapi.staticfiles import StaticFiles
|
| 14 |
|
| 15 |
|
| 16 |
API_BASE_URL = os.environ.get(
|
|
|
|
| 42 |
<a href="#timeline">Timeline</a>
|
| 43 |
<a href="#prizes">Prizes</a>
|
| 44 |
<a href="#organizers">Organizers</a>
|
| 45 |
+
<a href="#registration">Registration</a>
|
| 46 |
</div>
|
| 47 |
</div>
|
| 48 |
<header class="alpasim-hero">
|
| 49 |
<video autoplay muted loop playsinline preload="auto" aria-label="AlpaSim E2E Closed Loop Challenge 2026 banner">
|
| 50 |
+
<source src="/{BANNER_PATH}" type="video/mp4" />
|
| 51 |
</video>
|
| 52 |
<div class="hero-overlay">
|
| 53 |
<div class="hero-overlay-inner">
|
|
|
|
| 203 |
</section>
|
| 204 |
</main>
|
| 205 |
"""
|
| 206 |
+
APP_EMBED_HTML = """
|
| 207 |
+
<section id="registration" class="challenge-app">
|
| 208 |
+
<h2><span>07</span> Registration</h2>
|
| 209 |
+
<iframe
|
| 210 |
+
class="gradio-frame"
|
| 211 |
+
src="/gradio/"
|
| 212 |
+
title="AlpaSim registration, leaderboard, and review app"
|
| 213 |
+
></iframe>
|
| 214 |
+
</section>
|
| 215 |
+
"""
|
| 216 |
BANNER_CSS = """
|
| 217 |
+
html {
|
| 218 |
+
scroll-behavior: smooth;
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
body {
|
| 222 |
+
margin: 0;
|
| 223 |
+
background: var(--bg);
|
| 224 |
+
color: var(--text);
|
| 225 |
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
:root,
|
| 229 |
.gradio-container {
|
| 230 |
--bg: #ffffff;
|
|
|
|
| 263 |
justify-content: space-between;
|
| 264 |
gap: 20px;
|
| 265 |
padding: 12px 24px;
|
| 266 |
+
margin: 0;
|
| 267 |
background: rgba(255, 255, 255, 0.92);
|
| 268 |
backdrop-filter: blur(8px);
|
| 269 |
border-bottom: 1px solid var(--border);
|
|
|
|
| 297 |
.alpasim-hero {
|
| 298 |
position: relative;
|
| 299 |
overflow: hidden;
|
| 300 |
+
margin: 0;
|
| 301 |
background: #000000;
|
| 302 |
border-bottom: 1px solid var(--border);
|
| 303 |
}
|
|
|
|
| 356 |
.challenge-info section {
|
| 357 |
padding: 48px 0;
|
| 358 |
border-bottom: 1px solid var(--border);
|
| 359 |
+
scroll-margin-top: 72px;
|
| 360 |
}
|
| 361 |
|
| 362 |
.challenge-info section:last-child {
|
|
|
|
| 522 |
margin: 0 auto;
|
| 523 |
}
|
| 524 |
|
| 525 |
+
.challenge-app {
|
| 526 |
+
max-width: 1100px;
|
| 527 |
+
margin: 0 auto;
|
| 528 |
+
padding: 48px 24px 72px;
|
| 529 |
+
scroll-margin-top: 72px;
|
| 530 |
+
}
|
| 531 |
+
|
| 532 |
+
.challenge-app h2 {
|
| 533 |
+
display: flex;
|
| 534 |
+
align-items: baseline;
|
| 535 |
+
gap: 12px;
|
| 536 |
+
margin: 0 0 18px;
|
| 537 |
+
font-size: 28px;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
.challenge-app h2 span {
|
| 541 |
+
color: #76B900;
|
| 542 |
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
| 543 |
+
font-size: 0.7em;
|
| 544 |
+
letter-spacing: 0.08em;
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
.gradio-frame {
|
| 548 |
+
display: block;
|
| 549 |
+
width: 100%;
|
| 550 |
+
min-height: 940px;
|
| 551 |
+
border: 1px solid var(--border);
|
| 552 |
+
border-radius: 8px;
|
| 553 |
+
background: #ffffff;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
@media (max-width: 720px) {
|
| 557 |
.topline {
|
| 558 |
align-items: flex-start;
|
|
|
|
| 587 |
padding: 0 18px;
|
| 588 |
}
|
| 589 |
|
| 590 |
+
.challenge-info section,
|
| 591 |
+
.challenge-app {
|
| 592 |
+
scroll-margin-top: 112px;
|
| 593 |
+
}
|
| 594 |
+
|
| 595 |
+
.challenge-app {
|
| 596 |
+
padding: 48px 18px 64px;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
.gradio-frame {
|
| 600 |
+
min-height: 1040px;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
.identity-strip {
|
| 604 |
+
padding: 0 18px 18px;
|
| 605 |
+
}
|
| 606 |
+
}
|
| 607 |
+
"""
|
| 608 |
+
GRADIO_CSS = """
|
| 609 |
+
:root,
|
| 610 |
+
.gradio-container {
|
| 611 |
+
--bg: #ffffff;
|
| 612 |
+
--panel: #f8f9fb;
|
| 613 |
+
--panel-2: #f1f3f7;
|
| 614 |
+
--border: #e4e7ec;
|
| 615 |
+
--text: #1a1d23;
|
| 616 |
+
--muted: #6b7280;
|
| 617 |
+
--button-primary-background-fill: #76B900;
|
| 618 |
+
--button-primary-background-fill-hover: #5F9500;
|
| 619 |
+
--button-primary-border-color: #76B900;
|
| 620 |
+
--button-primary-text-color: white;
|
| 621 |
+
--color-accent: #76B900;
|
| 622 |
+
--link-text-color: #1f6feb;
|
| 623 |
+
--loader-color: #76B900;
|
| 624 |
+
--slider-color: #76B900;
|
| 625 |
+
color: var(--text);
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
.gradio-container {
|
| 629 |
+
background: var(--bg);
|
| 630 |
+
max-width: none !important;
|
| 631 |
+
padding-top: 0 !important;
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
.identity-strip {
|
| 635 |
+
max-width: 1100px;
|
| 636 |
+
margin: 0 auto;
|
| 637 |
+
padding: 0 24px 18px;
|
| 638 |
+
}
|
| 639 |
+
|
| 640 |
+
.identity-strip p {
|
| 641 |
+
margin: 0;
|
| 642 |
+
color: var(--muted);
|
| 643 |
+
}
|
| 644 |
+
|
| 645 |
+
.tabs {
|
| 646 |
+
max-width: 1100px;
|
| 647 |
+
margin: 0 auto;
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
@media (max-width: 720px) {
|
| 651 |
.identity-strip {
|
| 652 |
padding: 0 18px 18px;
|
| 653 |
}
|
| 654 |
}
|
| 655 |
"""
|
| 656 |
+
LANDING_HTML = f"""<!doctype html>
|
| 657 |
+
<html lang="en">
|
| 658 |
+
<head>
|
| 659 |
+
<meta charset="utf-8" />
|
| 660 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 661 |
+
<title>AlpaSim E2E Closed Loop Challenge 2026</title>
|
| 662 |
+
<style>{BANNER_CSS}</style>
|
| 663 |
+
</head>
|
| 664 |
+
<body>
|
| 665 |
+
{BANNER_HTML}
|
| 666 |
+
{CHALLENGE_OVERVIEW_HTML}
|
| 667 |
+
{APP_EMBED_HTML}
|
| 668 |
+
</body>
|
| 669 |
+
</html>
|
| 670 |
+
"""
|
| 671 |
|
| 672 |
|
| 673 |
def split_multivalue(value: str) -> list[str]:
|
|
|
|
| 696 |
"To register a team, connect this Space to your Hugging Face account. "
|
| 697 |
"If you are already signed in on huggingface.co, this just authorizes "
|
| 698 |
"the Space to read your Hugging Face profile. "
|
| 699 |
+
"[Connect Hugging Face account](/gradio/login/huggingface?_target_url=/gradio/)"
|
| 700 |
)
|
| 701 |
|
| 702 |
username = profile_value(profile, "preferred_username", "name")
|
|
|
|
| 707 |
parts.append(f"Stable HF user ID: `{user_id}`.")
|
| 708 |
if email:
|
| 709 |
parts.append(f"HF email: `{email}`.")
|
| 710 |
+
parts.append("[Disconnect](/gradio/logout?_target_url=/gradio/).")
|
| 711 |
return " ".join(parts)
|
| 712 |
|
| 713 |
|
|
|
|
| 1321 |
with gr.Blocks(
|
| 1322 |
title="AlpaSim E2E Closed Loop Challenge 2026",
|
| 1323 |
theme=APP_THEME,
|
| 1324 |
+
css=GRADIO_CSS,
|
| 1325 |
) as demo:
|
| 1326 |
add_oauth_route_marker()
|
|
|
|
|
|
|
| 1327 |
with gr.Group(elem_classes=["identity-strip"]):
|
| 1328 |
identity = gr.Markdown()
|
| 1329 |
|
|
|
|
| 1580 |
],
|
| 1581 |
)
|
| 1582 |
|
| 1583 |
+
app = FastAPI(title="AlpaSim E2E Closed Loop Challenge 2026")
|
| 1584 |
+
app.mount("/assets", StaticFiles(directory=str(BANNER_DIR)), name="assets")
|
| 1585 |
+
|
| 1586 |
+
|
| 1587 |
+
@app.get("/", response_class=HTMLResponse, include_in_schema=False)
|
| 1588 |
+
def landing_page() -> HTMLResponse:
|
| 1589 |
+
return HTMLResponse(LANDING_HTML)
|
| 1590 |
+
|
| 1591 |
+
|
| 1592 |
+
app = gr.mount_gradio_app(
|
| 1593 |
+
app,
|
| 1594 |
+
demo,
|
| 1595 |
+
path="/gradio",
|
| 1596 |
+
allowed_paths=[str(BANNER_FILE)],
|
| 1597 |
+
)
|
| 1598 |
+
|
| 1599 |
+
|
| 1600 |
if __name__ == "__main__":
|
| 1601 |
+
import uvicorn
|
| 1602 |
+
|
| 1603 |
+
uvicorn.run(
|
| 1604 |
+
app,
|
| 1605 |
+
host="0.0.0.0",
|
| 1606 |
+
port=int(os.environ.get("PORT", "7860")),
|
| 1607 |
+
)
|
requirements.txt
CHANGED
|
@@ -1 +1,3 @@
|
|
| 1 |
-
gradio[oauth]>=5.0
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio[oauth]>=5.0,<6.0
|
| 2 |
+
fastapi>=0.115
|
| 3 |
+
uvicorn>=0.30
|