Upload 3 files
Browse files- CHANGELOG.md +9 -0
- README.md +8 -16
- app.py +58 -34
CHANGELOG.md
CHANGED
|
@@ -9,6 +9,15 @@
|
|
| 9 |
|
| 10 |
# Changelog
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
## v31 — Functional feedback verification
|
| 13 |
|
| 14 |
- Added stronger Custom UI feedback states so bucket checks, build launch, validation launch, and polling always show visible status.
|
|
|
|
| 9 |
|
| 10 |
# Changelog
|
| 11 |
|
| 12 |
+
## V33 — Root custom dashboard
|
| 13 |
+
|
| 14 |
+
- Serve the custom dashboard directly at `/` and `/custom`.
|
| 15 |
+
- Move the legacy Gradio controls behind `/_gradio` for OAuth/debug only.
|
| 16 |
+
- Remove the product-facing Gradio iframe/preview experience.
|
| 17 |
+
- Keep OAuth-backed API routes and custom dashboard behavior intact.
|
| 18 |
+
- Add `uvicorn` as an explicit runtime dependency.
|
| 19 |
+
|
| 20 |
+
|
| 21 |
## v31 — Functional feedback verification
|
| 22 |
|
| 23 |
- Added stronger Custom UI feedback states so bucket checks, build launch, validation launch, and polling always show visible status.
|
README.md
CHANGED
|
@@ -26,7 +26,9 @@ model card → agentic job → generated private Space → hardware if available
|
|
| 26 |
|
| 27 |
## Custom UI
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
|
| 31 |
- bucket onboarding, including check/create before build;
|
| 32 |
- build launch with explicit ZeroGPU-first and fixed-GPU fallback controls;
|
|
@@ -35,7 +37,11 @@ A custom dashboard is available at `/custom`. It is wired to the same OAuth-back
|
|
| 35 |
- recent run exploration from the signed-in user's Bucket;
|
| 36 |
- quick links to the HF Job, generated Space, Space settings, and run artifacts.
|
| 37 |
|
| 38 |
-
The
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
## V26 custom Run Explorer with selected-run actions
|
| 41 |
|
|
@@ -117,20 +123,6 @@ Generated Spaces no longer pin `huggingface_hub<1.0.0`; modern model cards may r
|
|
| 117 |
Hardware decisions are written to `hardware_strategy.json` and `hardware_attempts.json` in the user run bucket.
|
| 118 |
|
| 119 |
|
| 120 |
-
## V24 custom UI shell
|
| 121 |
-
|
| 122 |
-
V24 introduces the first custom-frontend pass without replacing the validated Gradio controls yet. The custom dashboard is served at `/custom` and embedded in the app as **Custom UI preview**. It includes:
|
| 123 |
-
|
| 124 |
-
- a product-style Build from model card screen,
|
| 125 |
-
- a live progress card and timeline model,
|
| 126 |
-
- quick links placeholders,
|
| 127 |
-
- a Run Explorer with selected-run actions shell for the connected bucket,
|
| 128 |
-
- static frontend assets under `web/static/`,
|
| 129 |
-
- a small API skeleton for app info, progress projection, and future run listing.
|
| 130 |
-
|
| 131 |
-
The OAuth-backed actions remain in the functional Gradio controls for this pass. The next custom UI pass should wire the frontend actions to authenticated backend endpoints while preserving the same per-user bucket and Job security model.
|
| 132 |
-
|
| 133 |
-
|
| 134 |
### Custom UI V27
|
| 135 |
|
| 136 |
The custom interface now includes run-level actions: select a previous run, open its Job/Space/Settings/Artifacts, or prepare the Validate existing Space form directly from that run. A manual hardware action panel appears when a run requires user hardware selection.
|
|
|
|
| 26 |
|
| 27 |
## Custom UI
|
| 28 |
|
| 29 |
+
The product root (`/`) is now the custom dashboard. `/custom` is kept as an alias for the same UI. Gradio is no longer shown in the normal product flow; a debug/auth mount exists under `/_gradio` only to preserve Hugging Face OAuth routes and internal diagnostics.
|
| 30 |
+
|
| 31 |
+
The custom dashboard supports:
|
| 32 |
|
| 33 |
- bucket onboarding, including check/create before build;
|
| 34 |
- build launch with explicit ZeroGPU-first and fixed-GPU fallback controls;
|
|
|
|
| 37 |
- recent run exploration from the signed-in user's Bucket;
|
| 38 |
- quick links to the HF Job, generated Space, Space settings, and run artifacts.
|
| 39 |
|
| 40 |
+
The UI follows the validated dashboard layout: Build from model card, live progress, validation metrics, recent events, Run Explorer, selected run details, report preview, and quick links are visible from the main cockpit. It prevents build launches until the user bucket is ready, validates required fields before launch, shows explicit busy/error/success states, and keeps retrying live progress with a visible warning if polling is temporarily unavailable. This avoids leaving users unsure whether the Job is still running.
|
| 41 |
+
|
| 42 |
+
### V33 root custom dashboard
|
| 43 |
+
|
| 44 |
+
V33 removes the Gradio iframe/product shell problem: the public root is the custom dashboard itself. The old Gradio controls are not part of the normal UI anymore.
|
| 45 |
|
| 46 |
## V26 custom Run Explorer with selected-run actions
|
| 47 |
|
|
|
|
| 123 |
Hardware decisions are written to `hardware_strategy.json` and `hardware_attempts.json` in the user run bucket.
|
| 124 |
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
### Custom UI V27
|
| 127 |
|
| 128 |
The custom interface now includes run-level actions: select a previous run, open its Job/Space/Settings/Artifacts, or prepare the Validate existing Space form directly from that run. A manual hardware action panel appears when a run requires user hardware selection.
|
app.py
CHANGED
|
@@ -5,8 +5,9 @@ from pathlib import Path
|
|
| 5 |
from typing import Any
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
-
|
| 9 |
-
from fastapi
|
|
|
|
| 10 |
|
| 11 |
from src.bucket import check_user_bucket, create_user_bucket, read_run_bundle, list_recent_runs
|
| 12 |
from src.config import settings, user_bucket_source
|
|
@@ -99,17 +100,18 @@ def _api_links(*, run_id: str | None, bucket_source: str | None, target_space: s
|
|
| 99 |
"artifacts_url": _run_artifacts_url(run_id, bucket_source),
|
| 100 |
}
|
| 101 |
|
| 102 |
-
def register_custom_routes(
|
| 103 |
-
"""Register the custom UI and OAuth-backed JSON endpoints."""
|
| 104 |
|
| 105 |
-
@
|
|
|
|
| 106 |
async def custom_index(): # type: ignore[no-untyped-def]
|
| 107 |
index_path = WEB_DIR / "index.html"
|
| 108 |
if not index_path.exists():
|
| 109 |
raise HTTPException(status_code=404, detail="Custom UI index not found")
|
| 110 |
return HTMLResponse(index_path.read_text(encoding="utf-8"))
|
| 111 |
|
| 112 |
-
@
|
| 113 |
async def custom_static(asset_path: str): # type: ignore[no-untyped-def]
|
| 114 |
path = (STATIC_DIR / asset_path).resolve()
|
| 115 |
if STATIC_DIR.resolve() not in path.parents and path != STATIC_DIR.resolve():
|
|
@@ -118,7 +120,15 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 118 |
raise HTTPException(status_code=404, detail="Asset not found")
|
| 119 |
return FileResponse(path)
|
| 120 |
|
| 121 |
-
@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
async def api_app_info(request: Request): # type: ignore[no-untyped-def]
|
| 123 |
ctx: dict[str, Any] | None = None
|
| 124 |
try:
|
|
@@ -128,17 +138,17 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 128 |
return JSONResponse(
|
| 129 |
{
|
| 130 |
"name": "Agentic Space Factory",
|
| 131 |
-
"version": "
|
| 132 |
"bucket_default": settings.bucket_name,
|
| 133 |
"workflows": ["build_from_model_card", "validate_existing_space", "runs_explorer"],
|
| 134 |
-
"custom_ui_status": "
|
| 135 |
"user": {"username": ctx["username"], "missing_scopes": ctx.get("missing_scopes", []), "warnings": ctx.get("warnings", [])} if ctx else None,
|
| 136 |
-
"login_url": "/login/huggingface",
|
| 137 |
-
"logout_url": "/logout",
|
| 138 |
}
|
| 139 |
)
|
| 140 |
|
| 141 |
-
@
|
| 142 |
async def api_me(request: Request): # type: ignore[no-untyped-def]
|
| 143 |
ctx = _oauth_context_from_request(request)
|
| 144 |
return JSONResponse(
|
|
@@ -155,29 +165,29 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 155 |
"missing_scopes": ctx.get("missing_scopes", []),
|
| 156 |
"warnings": ctx.get("warnings", []),
|
| 157 |
"expires_at": ctx.get("expires_at"),
|
| 158 |
-
"login_url": "/login/huggingface",
|
| 159 |
-
"logout_url": "/logout",
|
| 160 |
}
|
| 161 |
)
|
| 162 |
|
| 163 |
-
@
|
| 164 |
async def api_oauth_diagnostics(request: Request): # type: ignore[no-untyped-def]
|
| 165 |
ctx = extract_oauth_context(request)
|
| 166 |
return JSONResponse({"user": public_oauth_context(ctx), "token_identity": verify_token_identity(ctx)})
|
| 167 |
|
| 168 |
-
@
|
| 169 |
async def api_bucket_status(request: Request, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 170 |
ctx = _oauth_context_from_request(request)
|
| 171 |
return JSONResponse(check_user_bucket(username=ctx["username"], bucket_name=bucket_name, token=ctx["token"]))
|
| 172 |
|
| 173 |
-
@
|
| 174 |
async def api_bucket_create(request: Request, payload: dict[str, Any] | None = None): # type: ignore[no-untyped-def]
|
| 175 |
ctx = _oauth_context_from_request(request)
|
| 176 |
payload = payload or {}
|
| 177 |
bucket_name = str(payload.get("bucket_name") or settings.bucket_name)
|
| 178 |
return JSONResponse(create_user_bucket(username=ctx["username"], bucket_name=bucket_name, token=ctx["token"]))
|
| 179 |
|
| 180 |
-
@
|
| 181 |
async def api_build(request: Request, payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 182 |
ctx = _oauth_context_from_request(request)
|
| 183 |
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
|
@@ -208,7 +218,7 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 208 |
)
|
| 209 |
return JSONResponse(result)
|
| 210 |
|
| 211 |
-
@
|
| 212 |
async def api_validate(request: Request, payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 213 |
ctx = _oauth_context_from_request(request)
|
| 214 |
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
|
@@ -241,7 +251,7 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 241 |
)
|
| 242 |
return JSONResponse(result)
|
| 243 |
|
| 244 |
-
@
|
| 245 |
async def api_progress_from_events(payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 246 |
events = payload.get("events") or []
|
| 247 |
state = payload.get("state") or {}
|
|
@@ -251,7 +261,7 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 251 |
raise HTTPException(status_code=400, detail="state must be an object")
|
| 252 |
return JSONResponse(progress_from_events(events, state=state))
|
| 253 |
|
| 254 |
-
@
|
| 255 |
async def api_runs( # type: ignore[no-untyped-def]
|
| 256 |
request: Request,
|
| 257 |
bucket_name: str = settings.bucket_name,
|
|
@@ -270,7 +280,7 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 270 |
}
|
| 271 |
)
|
| 272 |
|
| 273 |
-
@
|
| 274 |
async def api_run_detail(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 275 |
ctx = _oauth_context_from_request(request)
|
| 276 |
run_id = validate_run_id(run_id)
|
|
@@ -278,7 +288,7 @@ def register_custom_routes(demo: gr.Blocks) -> None:
|
|
| 278 |
bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
|
| 279 |
return JSONResponse({"run_id": run_id, "bucket_source": bucket_source, **bundle})
|
| 280 |
|
| 281 |
-
@
|
| 282 |
async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 283 |
ctx = _oauth_context_from_request(request)
|
| 284 |
run_id = validate_run_id(run_id)
|
|
@@ -556,15 +566,6 @@ def build_demo() -> gr.Blocks:
|
|
| 556 |
login_status = gr.Markdown()
|
| 557 |
demo.load(fn=get_login_status, inputs=None, outputs=login_status)
|
| 558 |
|
| 559 |
-
with gr.Tab("Custom UI preview"):
|
| 560 |
-
gr.Markdown(
|
| 561 |
-
"""
|
| 562 |
-
## Custom UI shell
|
| 563 |
-
|
| 564 |
-
This is the first custom-frontend pass. The shell, CSS, progress timeline, and API skeleton are available below. OAuth-backed actions remain in the functional Gradio tabs while the custom auth bridge is completed.
|
| 565 |
-
"""
|
| 566 |
-
)
|
| 567 |
-
gr.HTML('<iframe src="/custom" style="width:100%; min-height:900px; border:1px solid #e5e7eb; border-radius:16px; background:white;"></iframe>')
|
| 568 |
|
| 569 |
gr.Markdown("## Run storage")
|
| 570 |
gr.Markdown(
|
|
@@ -751,9 +752,32 @@ It cannot guarantee that every model card becomes a working Space. It cannot byp
|
|
| 751 |
"""
|
| 752 |
)
|
| 753 |
|
| 754 |
-
register_custom_routes(demo)
|
| 755 |
return demo
|
| 756 |
|
| 757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
if __name__ == "__main__":
|
| 759 |
-
|
|
|
|
| 5 |
from typing import Any
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
+
import uvicorn
|
| 9 |
+
from fastapi import FastAPI, HTTPException, Request
|
| 10 |
+
from fastapi.responses import HTMLResponse, JSONResponse, FileResponse, RedirectResponse
|
| 11 |
|
| 12 |
from src.bucket import check_user_bucket, create_user_bucket, read_run_bundle, list_recent_runs
|
| 13 |
from src.config import settings, user_bucket_source
|
|
|
|
| 100 |
"artifacts_url": _run_artifacts_url(run_id, bucket_source),
|
| 101 |
}
|
| 102 |
|
| 103 |
+
def register_custom_routes(fastapi_app: FastAPI) -> None:
|
| 104 |
+
"""Register the root custom UI and OAuth-backed JSON endpoints."""
|
| 105 |
|
| 106 |
+
@fastapi_app.get("/", response_class=HTMLResponse)
|
| 107 |
+
@fastapi_app.get("/custom", response_class=HTMLResponse)
|
| 108 |
async def custom_index(): # type: ignore[no-untyped-def]
|
| 109 |
index_path = WEB_DIR / "index.html"
|
| 110 |
if not index_path.exists():
|
| 111 |
raise HTTPException(status_code=404, detail="Custom UI index not found")
|
| 112 |
return HTMLResponse(index_path.read_text(encoding="utf-8"))
|
| 113 |
|
| 114 |
+
@fastapi_app.get("/custom-static/{asset_path:path}")
|
| 115 |
async def custom_static(asset_path: str): # type: ignore[no-untyped-def]
|
| 116 |
path = (STATIC_DIR / asset_path).resolve()
|
| 117 |
if STATIC_DIR.resolve() not in path.parents and path != STATIC_DIR.resolve():
|
|
|
|
| 120 |
raise HTTPException(status_code=404, detail="Asset not found")
|
| 121 |
return FileResponse(path)
|
| 122 |
|
| 123 |
+
@fastapi_app.get("/login/huggingface")
|
| 124 |
+
async def login_redirect(): # type: ignore[no-untyped-def]
|
| 125 |
+
return RedirectResponse("/_gradio/login/huggingface")
|
| 126 |
+
|
| 127 |
+
@fastapi_app.get("/logout")
|
| 128 |
+
async def logout_redirect(): # type: ignore[no-untyped-def]
|
| 129 |
+
return RedirectResponse("/_gradio/logout")
|
| 130 |
+
|
| 131 |
+
@fastapi_app.get("/api/app-info")
|
| 132 |
async def api_app_info(request: Request): # type: ignore[no-untyped-def]
|
| 133 |
ctx: dict[str, Any] | None = None
|
| 134 |
try:
|
|
|
|
| 138 |
return JSONResponse(
|
| 139 |
{
|
| 140 |
"name": "Agentic Space Factory",
|
| 141 |
+
"version": "v33-custom-root-ui",
|
| 142 |
"bucket_default": settings.bucket_name,
|
| 143 |
"workflows": ["build_from_model_card", "validate_existing_space", "runs_explorer"],
|
| 144 |
+
"custom_ui_status": "root_custom_ui",
|
| 145 |
"user": {"username": ctx["username"], "missing_scopes": ctx.get("missing_scopes", []), "warnings": ctx.get("warnings", [])} if ctx else None,
|
| 146 |
+
"login_url": "/_gradio/login/huggingface",
|
| 147 |
+
"logout_url": "/_gradio/logout",
|
| 148 |
}
|
| 149 |
)
|
| 150 |
|
| 151 |
+
@fastapi_app.get("/api/me")
|
| 152 |
async def api_me(request: Request): # type: ignore[no-untyped-def]
|
| 153 |
ctx = _oauth_context_from_request(request)
|
| 154 |
return JSONResponse(
|
|
|
|
| 165 |
"missing_scopes": ctx.get("missing_scopes", []),
|
| 166 |
"warnings": ctx.get("warnings", []),
|
| 167 |
"expires_at": ctx.get("expires_at"),
|
| 168 |
+
"login_url": "/_gradio/login/huggingface",
|
| 169 |
+
"logout_url": "/_gradio/logout",
|
| 170 |
}
|
| 171 |
)
|
| 172 |
|
| 173 |
+
@fastapi_app.get("/api/oauth/diagnostics")
|
| 174 |
async def api_oauth_diagnostics(request: Request): # type: ignore[no-untyped-def]
|
| 175 |
ctx = extract_oauth_context(request)
|
| 176 |
return JSONResponse({"user": public_oauth_context(ctx), "token_identity": verify_token_identity(ctx)})
|
| 177 |
|
| 178 |
+
@fastapi_app.get("/api/bucket/status")
|
| 179 |
async def api_bucket_status(request: Request, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 180 |
ctx = _oauth_context_from_request(request)
|
| 181 |
return JSONResponse(check_user_bucket(username=ctx["username"], bucket_name=bucket_name, token=ctx["token"]))
|
| 182 |
|
| 183 |
+
@fastapi_app.post("/api/bucket/create")
|
| 184 |
async def api_bucket_create(request: Request, payload: dict[str, Any] | None = None): # type: ignore[no-untyped-def]
|
| 185 |
ctx = _oauth_context_from_request(request)
|
| 186 |
payload = payload or {}
|
| 187 |
bucket_name = str(payload.get("bucket_name") or settings.bucket_name)
|
| 188 |
return JSONResponse(create_user_bucket(username=ctx["username"], bucket_name=bucket_name, token=ctx["token"]))
|
| 189 |
|
| 190 |
+
@fastapi_app.post("/api/build")
|
| 191 |
async def api_build(request: Request, payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 192 |
ctx = _oauth_context_from_request(request)
|
| 193 |
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
|
|
|
| 218 |
)
|
| 219 |
return JSONResponse(result)
|
| 220 |
|
| 221 |
+
@fastapi_app.post("/api/validate")
|
| 222 |
async def api_validate(request: Request, payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 223 |
ctx = _oauth_context_from_request(request)
|
| 224 |
bucket_name = payload.get("bucket_name") or settings.bucket_name
|
|
|
|
| 251 |
)
|
| 252 |
return JSONResponse(result)
|
| 253 |
|
| 254 |
+
@fastapi_app.post("/api/progress/from-events")
|
| 255 |
async def api_progress_from_events(payload: dict[str, Any]): # type: ignore[no-untyped-def]
|
| 256 |
events = payload.get("events") or []
|
| 257 |
state = payload.get("state") or {}
|
|
|
|
| 261 |
raise HTTPException(status_code=400, detail="state must be an object")
|
| 262 |
return JSONResponse(progress_from_events(events, state=state))
|
| 263 |
|
| 264 |
+
@fastapi_app.get("/api/runs")
|
| 265 |
async def api_runs( # type: ignore[no-untyped-def]
|
| 266 |
request: Request,
|
| 267 |
bucket_name: str = settings.bucket_name,
|
|
|
|
| 280 |
}
|
| 281 |
)
|
| 282 |
|
| 283 |
+
@fastapi_app.get("/api/runs/{run_id}")
|
| 284 |
async def api_run_detail(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 285 |
ctx = _oauth_context_from_request(request)
|
| 286 |
run_id = validate_run_id(run_id)
|
|
|
|
| 288 |
bundle = read_run_bundle(run_id, bucket_source=bucket_source, token=ctx["token"])
|
| 289 |
return JSONResponse({"run_id": run_id, "bucket_source": bucket_source, **bundle})
|
| 290 |
|
| 291 |
+
@fastapi_app.get("/api/runs/{run_id}/progress")
|
| 292 |
async def api_run_progress(request: Request, run_id: str, bucket_name: str = settings.bucket_name): # type: ignore[no-untyped-def]
|
| 293 |
ctx = _oauth_context_from_request(request)
|
| 294 |
run_id = validate_run_id(run_id)
|
|
|
|
| 566 |
login_status = gr.Markdown()
|
| 567 |
demo.load(fn=get_login_status, inputs=None, outputs=login_status)
|
| 568 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
gr.Markdown("## Run storage")
|
| 571 |
gr.Markdown(
|
|
|
|
| 752 |
"""
|
| 753 |
)
|
| 754 |
|
|
|
|
| 755 |
return demo
|
| 756 |
|
| 757 |
|
| 758 |
+
def create_app() -> FastAPI:
|
| 759 |
+
"""Create the product app.
|
| 760 |
+
|
| 761 |
+
The public root path is the custom dashboard. A Gradio fallback/debug UI is
|
| 762 |
+
mounted under `/_gradio` only to preserve Hugging Face OAuth routes and for
|
| 763 |
+
internal diagnostics; users should not see Gradio in the normal product flow.
|
| 764 |
+
"""
|
| 765 |
+
fastapi_app = FastAPI(title="Agentic Space Factory")
|
| 766 |
+
register_custom_routes(fastapi_app)
|
| 767 |
+
try:
|
| 768 |
+
debug_demo = build_demo()
|
| 769 |
+
return gr.mount_gradio_app(fastapi_app, debug_demo, path="/_gradio")
|
| 770 |
+
except ValueError as exc:
|
| 771 |
+
# Local development without an HF token can make Gradio's mocked OAuth
|
| 772 |
+
# routes fail at import time. Keep the custom UI importable and testable.
|
| 773 |
+
# In Spaces, where HF OAuth is configured, the Gradio auth mount is used.
|
| 774 |
+
if "HF_TOKEN" not in str(exc) and "logged in to HF" not in str(exc):
|
| 775 |
+
raise
|
| 776 |
+
return fastapi_app
|
| 777 |
+
|
| 778 |
+
|
| 779 |
+
app = create_app()
|
| 780 |
+
|
| 781 |
+
|
| 782 |
if __name__ == "__main__":
|
| 783 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|