| from __future__ import annotations |
|
|
| import base64 |
| import json |
| import mimetypes |
| import os |
| from pathlib import Path |
| from typing import Any |
|
|
| from fastapi import Body |
| from fastapi.responses import FileResponse, JSONResponse |
| from fastapi.staticfiles import StaticFiles |
| import gradio as gr |
| from gradio.workflow import WRITE_TOKEN |
| from gradio import Server |
|
|
| from diy_lab.epic_errands_diy_workflow.workflow_adapter import build_workflow_canvas_app |
| from epic_errands_v2.generation import ( |
| build_app_bootstrap, |
| build_diy_state, |
| build_generated_goal, |
| build_generated_goal_with_live_fallback, |
| ) |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| FRONTEND_ROOT = ROOT / "frontend" |
| ASSETS_ROOT = FRONTEND_ROOT / "assets" |
| DIY_FRONTEND_ROOT = ROOT / "diy_lab" / "frontend" |
| DIY_WORKFLOW_GRAPH_PATH = ROOT / "diy_lab" / "workflow.json" |
| LOCAL_WORKFLOW_WRITE_COOKIE = "gradio_workflow_write_token_epic_errands_v2" |
|
|
|
|
| def build_server() -> Server: |
| app = Server( |
| title="Epic Errands V2", |
| description="A local V2 mobile UI served by a Gradio-compatible Server.", |
| docs_url=None, |
| redoc_url=None, |
| ) |
|
|
| app.mount("/frontend", StaticFiles(directory=FRONTEND_ROOT), name="frontend") |
| app.mount("/epic", StaticFiles(directory=FRONTEND_ROOT), name="epic_frontend") |
| app.mount("/assets", StaticFiles(directory=ASSETS_ROOT), name="assets") |
| app.mount("/diy-assets", StaticFiles(directory=DIY_FRONTEND_ROOT), name="diy_assets") |
| app.mount("/epic-diy", StaticFiles(directory=DIY_FRONTEND_ROOT), name="epic_diy_assets") |
| workflow_app = build_workflow_canvas_app(DIY_WORKFLOW_GRAPH_PATH) |
| if workflow_app is not None: |
| gr.mount_gradio_app( |
| app, |
| workflow_app, |
| path="/diy-workflow", |
| footer_links=[], |
| show_error=True, |
| ) |
|
|
| @app.get("/") |
| async def homepage() -> FileResponse: |
| return FileResponse(FRONTEND_ROOT / "index.html") |
|
|
| def diy_file_response() -> FileResponse: |
| response = FileResponse(DIY_FRONTEND_ROOT / "index.html") |
| if not os.environ.get("SPACE_ID"): |
| response.set_cookie( |
| LOCAL_WORKFLOW_WRITE_COOKIE, |
| WRITE_TOKEN, |
| path="/", |
| httponly=True, |
| samesite="lax", |
| ) |
| return response |
|
|
| @app.get("/diy") |
| async def diy_homepage() -> FileResponse: |
| return diy_file_response() |
|
|
| @app.get("/diy/") |
| async def diy_homepage_slash() -> FileResponse: |
| return diy_file_response() |
|
|
| @app.get("/health") |
| async def health() -> dict[str, str]: |
| return { |
| "status": "ok", |
| "app": "epic-errands-v2", |
| "frontend": "fully-custom-ui", |
| "version": "v2", |
| "proof_claim": "local_implementation_only", |
| "live_generation": _live_generation_label(), |
| "quality_text_model": "nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF", |
| "quality_image_model": "black-forest-labs/FLUX.2-klein-9B", |
| "quality_audio_model": "openbmb/VoxCPM2", |
| "space_id": "set" if os.environ.get("SPACE_ID") else "missing", |
| } |
|
|
| @app.get("/api/bootstrap") |
| async def bootstrap() -> JSONResponse: |
| return JSONResponse(build_app_bootstrap()) |
|
|
| @app.post("/api/generate-goal") |
| async def generate_goal(payload: dict[str, Any] = Body(default_factory=dict)) -> JSONResponse: |
| return JSONResponse( |
| build_generated_goal_with_live_fallback( |
| str(payload.get("ordinary_goal") or ""), |
| str( |
| payload.get("ui_theme_id") |
| or payload.get("theme_id_at_creation") |
| or payload.get("theme_id") |
| or "questbook" |
| ), |
| selected_generation_reference_ids=[ |
| str(item) for item in payload.get("selected_generation_reference_ids", []) if str(item).strip() |
| ], |
| audio_used_parent_reference=bool(payload.get("audio_used_parent_reference")), |
| ) |
| ) |
|
|
| @app.post("/api/diy-preview") |
| async def diy_preview(payload: dict[str, Any] = Body(default_factory=dict)) -> JSONResponse: |
| return JSONResponse( |
| build_diy_state( |
| str(payload.get("theme_id") or payload.get("selected_theme_id") or "questbook"), |
| str(payload.get("ordinary_goal") or ""), |
| selected_generation_reference_ids=[ |
| str(item) for item in payload.get("selected_generation_reference_ids", []) if str(item).strip() |
| ], |
| ) |
| ) |
|
|
| return app |
|
|
|
|
| def build_hosted_demo() -> gr.Blocks: |
| """HF Gradio SDK entrypoint: render the custom UI without an iframe.""" |
| with gr.Blocks( |
| title="Epic Errands V2", |
| ) as demo: |
| gr.HTML( |
| value=_hosted_html(), |
| elem_id="epic-hosted-shell", |
| container=False, |
| padding=False, |
| js_on_load=_hosted_js(), |
| ) |
| backend_request = gr.Textbox(visible=False, container=False, label="Backend request") |
| backend_response = gr.Textbox(visible=False, container=False, label="Backend response") |
| gr.Button("Bootstrap", visible=False).click( |
| fn=_hosted_bootstrap, |
| inputs=backend_request, |
| outputs=backend_response, |
| api_name="epic_bootstrap", |
| show_progress="hidden", |
| queue=True, |
| ) |
| gr.Button("Generate Goal", visible=False).click( |
| fn=_hosted_generate_goal, |
| inputs=backend_request, |
| outputs=backend_response, |
| api_name="epic_generate_goal", |
| show_progress="hidden", |
| queue=True, |
| ) |
| gr.Button("DIY Preview", visible=False).click( |
| fn=_hosted_diy_preview, |
| inputs=backend_request, |
| outputs=backend_response, |
| api_name="epic_diy_preview", |
| show_progress="hidden", |
| queue=True, |
| ) |
| return demo |
|
|
|
|
| def _hosted_css() -> str: |
| css = "\n".join( |
| [ |
| (FRONTEND_ROOT / "styles" / "tokens.css").read_text(encoding="utf-8"), |
| (FRONTEND_ROOT / "styles" / "components.css").read_text(encoding="utf-8"), |
| (FRONTEND_ROOT / "styles" / "app.css").read_text(encoding="utf-8"), |
| (DIY_FRONTEND_ROOT / "styles" / "diy.css").read_text(encoding="utf-8"), |
| ] |
| ) |
| hosted_overrides = ( |
| ":root{color-scheme:light;}" |
| "html,body{background:#f4f5f7!important;color-scheme:light!important;}" |
| "gradio-app,.gradio-container{color-scheme:light!important;}" |
| ".gradio-container{max-width:none!important;padding:0!important;background:#f4f5f7!important;color:#353a42!important;}" |
| ".dark .gradio-container,.gradio-container.dark{background:#f4f5f7!important;color:#353a42!important;}" |
| "footer{display:none!important;}" |
| "#epic-hosted-shell{padding:0!important;margin:0!important;background:var(--page-bg)!important;color:var(--body-color)!important;}" |
| "#epic-hosted-shell .card,#epic-hosted-shell .card-inner{color:var(--body-color)!important;font-family:var(--font-body)!important;}" |
| "#epic-hosted-shell .wordmark .display,#epic-hosted-shell .lead h1,#epic-hosted-shell .panel-head h2,#epic-hosted-shell .section__title,#epic-hosted-shell .parent__head h3,#epic-hosted-shell .field .big,#epic-hosted-shell .field .pts,#epic-hosted-shell .kidnote b{color:var(--heading-color)!important;font-family:var(--font-display)!important;}" |
| "#epic-hosted-shell .wordmark .sub,#epic-hosted-shell .lead p,#epic-hosted-shell .panel-head span,#epic-hosted-shell .section__sub,#epic-hosted-shell .empty-line,#epic-hosted-shell .kidnote,#epic-hosted-shell .foot{color:var(--muted-color)!important;font-family:var(--font-body)!important;}" |
| "#epic-hosted-shell .tab-btn{background:var(--panel-bg)!important;color:var(--muted-color)!important;border-color:var(--panel-border-color)!important;font-family:var(--font-body)!important;font-size:11px!important;font-weight:850!important;}" |
| "#epic-hosted-shell .tab-btn[aria-pressed='true']{background:var(--accent-soft)!important;color:var(--accent-ink)!important;border-color:var(--accent)!important;}" |
| "#epic-hosted-shell .tab-btn b{background:var(--reward)!important;color:#17120c!important;}" |
| "#epic-hosted-shell .seg-btn,#epic-hosted-shell .mode-btn,#epic-hosted-shell .ref-chip{background:var(--field-bg)!important;color:var(--field-fg)!important;border-color:var(--field-border-color)!important;font-family:var(--font-body)!important;font-weight:850!important;}" |
| "#epic-hosted-shell .seg-btn[aria-pressed='true'],#epic-hosted-shell .mode-btn[aria-pressed='true'],#epic-hosted-shell .ref-chip[aria-pressed='true']{background:var(--accent-soft)!important;color:var(--accent-ink)!important;border-color:var(--accent)!important;}" |
| "#epic-hosted-shell .btn-primary{background:var(--primary)!important;color:var(--primary-text)!important;border:0!important;font-family:var(--font-display)!important;font-weight:var(--display-weight)!important;text-transform:var(--display-transform)!important;letter-spacing:var(--display-tracking)!important;box-shadow:var(--primary-shadow)!important;}" |
| "#epic-hosted-shell .btn-ghost,#epic-hosted-shell .field__input,#epic-hosted-shell .upload-chip{background:var(--field-bg)!important;color:var(--field-fg)!important;border-color:var(--field-border-color)!important;font-family:var(--font-body)!important;}" |
| "#epic-hosted-shell .tab-btn span,#epic-hosted-shell .tab-btn svg,#epic-hosted-shell .seg-btn span,#epic-hosted-shell .mode-btn span,#epic-hosted-shell .ref-chip span,#epic-hosted-shell .ref-chip small,#epic-hosted-shell .btn-primary span,#epic-hosted-shell .btn-primary svg,#epic-hosted-shell .btn-ghost span,#epic-hosted-shell .btn-ghost svg,#epic-hosted-shell .upload-chip span{color:inherit!important;}" |
| "#epic-hosted-shell .quest-headline .eyebrow{color:var(--banner-text)!important;font-family:var(--font-display)!important;}" |
| "#epic-hosted-shell .quest-headline .quest-title{color:var(--quest-title-color)!important;font-family:var(--font-display)!important;}" |
| ) |
| return ( |
| hosted_overrides |
| + css |
| + hosted_overrides |
| ) |
|
|
|
|
| def _hosted_html() -> str: |
| return f'<style>{_hosted_css()}</style><main class="stage" aria-label="Epic Errands V2"><div id="app"></div></main>' |
|
|
|
|
| def _hosted_js() -> str: |
| js = (FRONTEND_ROOT / "scripts" / "app.js").read_text(encoding="utf-8") |
| manifest = { |
| str(path.relative_to(ASSETS_ROOT)): _data_uri(path) |
| for path in sorted(ASSETS_ROOT.rglob("*")) |
| if path.is_file() |
| } |
| bootstrap = ( |
| "window.EPIC_ASSET_BASE = '';" |
| "window.EPIC_EMBEDDED_SPACE_MODE = true;" |
| "window.EPIC_GRADIO_API_PREFIX = '/gradio_api';" |
| f"window.EPIC_ASSET_MANIFEST = {json.dumps(manifest, separators=(',', ':'))};" |
| ) |
| return bootstrap + "\n" + js |
|
|
|
|
| def _decode_request(payload_json: str | None) -> dict[str, Any]: |
| if not payload_json: |
| return {} |
| try: |
| payload = json.loads(payload_json) |
| except json.JSONDecodeError: |
| return {} |
| return payload if isinstance(payload, dict) else {} |
|
|
|
|
| def _hosted_bootstrap(payload_json: str | None = None) -> str: |
| payload = build_app_bootstrap() |
| payload["backend_bridge"] = { |
| "transport": "gradio_blocks_named_api", |
| "bootstrap_endpoint": "epic_bootstrap", |
| "generate_endpoint": "epic_generate_goal", |
| "diy_preview_endpoint": "epic_diy_preview", |
| "model_runtime": "none", |
| "model_backend": "static_review_asset", |
| "fallback_used": True, |
| } |
| return json.dumps(payload, separators=(",", ":")) |
|
|
|
|
| def _hosted_generate_goal(payload_json: str | None = None) -> str: |
| payload = _decode_request(payload_json) |
| generated = build_generated_goal( |
| str(payload.get("ordinary_goal") or ""), |
| str( |
| payload.get("ui_theme_id") |
| or payload.get("theme_id_at_creation") |
| or payload.get("theme_id") |
| or "questbook" |
| ), |
| selected_generation_reference_ids=[ |
| str(item) for item in payload.get("selected_generation_reference_ids", []) if str(item).strip() |
| ], |
| audio_used_parent_reference=bool(payload.get("audio_used_parent_reference")), |
| ) |
| fallback_used = bool(generated.get("provenance", {}).get("fallback_used", True)) |
| generated["provenance"] = { |
| **generated.get("provenance", {}), |
| "app_host": "hf_space_gradio_blocks", |
| "model_runtime": generated.get("provenance", {}).get("model_runtime") or ("none" if fallback_used else "modal"), |
| "model_backend": generated.get("provenance", {}).get("model_backend") or ("static_review_asset" if fallback_used else "modal_http"), |
| "backend_transport": "gradio_blocks_named_api", |
| "api_name": "epic_generate_goal", |
| "fallback_reason": generated.get("provenance", {}).get("fallback_reason") |
| or ( |
| "partial fallback; see modal_runtime_steps" |
| if fallback_used and generated.get("provenance", {}).get("model_runtime") == "modal" |
| else "deterministic hosted dry-run; Modal/live generation disabled or unavailable" |
| if fallback_used |
| else "" |
| ), |
| } |
| return json.dumps(generated, separators=(",", ":")) |
|
|
|
|
| def _hosted_diy_preview(payload_json: str | None = None) -> str: |
| payload = _decode_request(payload_json) |
| preview = build_diy_state( |
| str(payload.get("theme_id") or payload.get("selected_theme_id") or "questbook"), |
| str(payload.get("ordinary_goal") or ""), |
| selected_generation_reference_ids=[ |
| str(item) for item in payload.get("selected_generation_reference_ids", []) if str(item).strip() |
| ], |
| ) |
| preview["backend_bridge"] = { |
| "transport": "gradio_blocks_named_api", |
| "api_name": "epic_diy_preview", |
| "model_runtime": "none", |
| "model_backend": "static_review_asset", |
| "fallback_used": True, |
| } |
| return json.dumps(preview, separators=(",", ":")) |
|
|
|
|
| def _data_uri(path: Path) -> str: |
| mime_type = mimetypes.guess_type(path.name)[0] or "application/octet-stream" |
| encoded = base64.b64encode(path.read_bytes()).decode("ascii") |
| return f"data:{mime_type};base64,{encoded}" |
|
|
|
|
| def _live_generation_label() -> str: |
| if os.environ.get("EPIC_ENABLE_LIVE_GENERATION", "").strip().lower() in {"1", "true", "yes"}: |
| return "enabled" |
| if os.environ.get("APP_DRY_RUN", "true").strip().lower() in {"0", "false", "no"}: |
| return "enabled" |
| return "disabled" |
|
|
|
|
| server = build_server() |
| app = build_hosted_demo() |
|
|
|
|
| if __name__ == "__main__": |
| launch_target = app if os.environ.get("SPACE_ID") else server |
| launch_target.launch( |
| server_name=os.environ.get("HOST", "0.0.0.0"), |
| server_port=int(os.environ.get("PORT", "7860")), |
| footer_links=[], |
| show_error=True, |
| ) |
|
|