curieous commited on
Commit
651bd05
·
verified ·
1 Parent(s): f443226

Deploy Epic Errands V2 Space

Browse files
README.md CHANGED
@@ -8,6 +8,16 @@ sdk_version: 6.18.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
  # Epic Errands V2 Mobile Review
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ tags:
12
+ - track:wood
13
+ - sponsor:openbmb
14
+ - sponsor:openai
15
+ - sponsor:nvidia
16
+ - sponsor:modal
17
+ - achievement:offbrand
18
+ - achievement:llama
19
+ - achievement:sharing
20
+ - achievement:fieldnotes
21
  ---
22
 
23
  # Epic Errands V2 Mobile Review
app.py CHANGED
@@ -15,7 +15,12 @@ from gradio.workflow import WRITE_TOKEN
15
  from gradio import Server
16
 
17
  from diy_lab.epic_errands_diy_workflow.workflow_adapter import build_workflow_canvas_app
18
- from epic_errands_v2.generation import build_app_bootstrap, build_diy_state, build_generated_goal
 
 
 
 
 
19
 
20
 
21
  ROOT = Path(__file__).resolve().parent
@@ -81,7 +86,7 @@ def build_server() -> Server:
81
  "frontend": "fully-custom-ui",
82
  "version": "v2",
83
  "proof_claim": "local_implementation_only",
84
- "live_generation": "disabled",
85
  "quality_text_model": "nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF",
86
  "quality_image_model": "black-forest-labs/FLUX.2-klein-9B",
87
  "quality_audio_model": "openbmb/VoxCPM2",
@@ -95,7 +100,7 @@ def build_server() -> Server:
95
  @app.post("/api/generate-goal")
96
  async def generate_goal(payload: dict[str, Any] = Body(default_factory=dict)) -> JSONResponse:
97
  return JSONResponse(
98
- build_generated_goal(
99
  str(payload.get("ordinary_goal") or ""),
100
  str(
101
  payload.get("ui_theme_id")
@@ -251,7 +256,7 @@ def _hosted_bootstrap(payload_json: str | None = None) -> str:
251
 
252
  def _hosted_generate_goal(payload_json: str | None = None) -> str:
253
  payload = _decode_request(payload_json)
254
- generated = build_generated_goal(
255
  str(payload.get("ordinary_goal") or ""),
256
  str(
257
  payload.get("ui_theme_id")
@@ -264,14 +269,16 @@ def _hosted_generate_goal(payload_json: str | None = None) -> str:
264
  ],
265
  audio_used_parent_reference=bool(payload.get("audio_used_parent_reference")),
266
  )
 
267
  generated["provenance"] = {
268
  **generated.get("provenance", {}),
269
  "app_host": "hf_space_gradio_blocks",
270
- "model_runtime": "none",
271
- "model_backend": "static_review_asset",
272
  "backend_transport": "gradio_blocks_named_api",
273
  "api_name": "epic_generate_goal",
274
- "fallback_reason": "deterministic hosted dry-run; Modal/live model not wired to hosted app",
 
275
  }
276
  return json.dumps(generated, separators=(",", ":"))
277
 
@@ -301,6 +308,14 @@ def _data_uri(path: Path) -> str:
301
  return f"data:{mime_type};base64,{encoded}"
302
 
303
 
 
 
 
 
 
 
 
 
304
  server = build_server()
305
  app = build_hosted_demo()
306
 
 
15
  from gradio import Server
16
 
17
  from diy_lab.epic_errands_diy_workflow.workflow_adapter import build_workflow_canvas_app
18
+ from epic_errands_v2.generation import (
19
+ build_app_bootstrap,
20
+ build_diy_state,
21
+ build_generated_goal,
22
+ build_generated_goal_with_live_fallback,
23
+ )
24
 
25
 
26
  ROOT = Path(__file__).resolve().parent
 
86
  "frontend": "fully-custom-ui",
87
  "version": "v2",
88
  "proof_claim": "local_implementation_only",
89
+ "live_generation": _live_generation_label(),
90
  "quality_text_model": "nvidia/NVIDIA-Nemotron-3-Nano-4B-GGUF",
91
  "quality_image_model": "black-forest-labs/FLUX.2-klein-9B",
92
  "quality_audio_model": "openbmb/VoxCPM2",
 
100
  @app.post("/api/generate-goal")
101
  async def generate_goal(payload: dict[str, Any] = Body(default_factory=dict)) -> JSONResponse:
102
  return JSONResponse(
103
+ build_generated_goal_with_live_fallback(
104
  str(payload.get("ordinary_goal") or ""),
105
  str(
106
  payload.get("ui_theme_id")
 
256
 
257
  def _hosted_generate_goal(payload_json: str | None = None) -> str:
258
  payload = _decode_request(payload_json)
259
+ generated = build_generated_goal_with_live_fallback(
260
  str(payload.get("ordinary_goal") or ""),
261
  str(
262
  payload.get("ui_theme_id")
 
269
  ],
270
  audio_used_parent_reference=bool(payload.get("audio_used_parent_reference")),
271
  )
272
+ fallback_used = bool(generated.get("provenance", {}).get("fallback_used", True))
273
  generated["provenance"] = {
274
  **generated.get("provenance", {}),
275
  "app_host": "hf_space_gradio_blocks",
276
+ "model_runtime": generated.get("provenance", {}).get("model_runtime") or ("none" if fallback_used else "modal"),
277
+ "model_backend": generated.get("provenance", {}).get("model_backend") or ("static_review_asset" if fallback_used else "modal_http"),
278
  "backend_transport": "gradio_blocks_named_api",
279
  "api_name": "epic_generate_goal",
280
+ "fallback_reason": generated.get("provenance", {}).get("fallback_reason")
281
+ or ("deterministic hosted dry-run; Modal/live generation disabled or unavailable" if fallback_used else ""),
282
  }
283
  return json.dumps(generated, separators=(",", ":"))
284
 
 
308
  return f"data:{mime_type};base64,{encoded}"
309
 
310
 
311
+ def _live_generation_label() -> str:
312
+ if os.environ.get("EPIC_ENABLE_LIVE_GENERATION", "").strip().lower() in {"1", "true", "yes"}:
313
+ return "enabled"
314
+ if os.environ.get("APP_DRY_RUN", "true").strip().lower() in {"0", "false", "no"}:
315
+ return "enabled"
316
+ return "disabled"
317
+
318
+
319
  server = build_server()
320
  app = build_hosted_demo()
321
 
diy_lab/epic_errands_diy_workflow/modal_client.py CHANGED
@@ -51,12 +51,20 @@ class ModalClientConfig:
51
  @classmethod
52
  def from_env(cls) -> "ModalClientConfig":
53
  dry_value = os.environ.get("APP_DRY_RUN", "true").strip().lower()
 
 
 
 
54
  return cls(
55
- dry_run=dry_value not in {"0", "false", "no"},
56
- base_url=os.environ.get("APP_MODAL_BASE_URL", ""),
57
- text_url=os.environ.get("APP_MODAL_TEXT_URL", ""),
58
- image_url=os.environ.get("APP_MODAL_IMAGE_URL", ""),
59
- audio_url=os.environ.get("APP_MODAL_AUDIO_URL", ""),
 
 
 
 
60
  auth_token=os.environ.get("APP_MODAL_AUTH_TOKEN", ""),
61
  timeout_seconds=float(os.environ.get("APP_MODAL_TIMEOUT_SECONDS", "45") or 45),
62
  model_id=os.environ.get("APP_MODAL_MODEL_ID", ""),
@@ -217,7 +225,7 @@ class ModalClient:
217
  def _metadata_from_modal(result: dict[str, Any]) -> RuntimeMetadata:
218
  return RuntimeMetadata(
219
  lifecycle_stage=str(result.get("lifecycle_stage") or "testing"),
220
- app_host="local",
221
  model_runtime=str(result.get("model_runtime") or "modal"),
222
  model_backend=str(result.get("model_backend") or "modal_http"),
223
  inference_engine=str(result.get("inference_engine") or "unknown"),
 
51
  @classmethod
52
  def from_env(cls) -> "ModalClientConfig":
53
  dry_value = os.environ.get("APP_DRY_RUN", "true").strip().lower()
54
+ live_value = os.environ.get("EPIC_ENABLE_LIVE_GENERATION", "").strip().lower()
55
+ dry_run = dry_value not in {"0", "false", "no"}
56
+ if live_value in {"1", "true", "yes"}:
57
+ dry_run = False
58
  return cls(
59
+ dry_run=dry_run,
60
+ base_url=os.environ.get("APP_MODAL_BASE_URL", "") or os.environ.get("EPIC_MODAL_BASE_URL", ""),
61
+ text_url=os.environ.get("APP_MODAL_TEXT_URL", "") or os.environ.get("EPIC_MODAL_TEXT_URL", ""),
62
+ image_url=os.environ.get("APP_MODAL_IMAGE_URL", "") or os.environ.get("EPIC_MODAL_IMAGE_URL", ""),
63
+ audio_url=(
64
+ os.environ.get("APP_MODAL_AUDIO_URL", "")
65
+ or os.environ.get("EPIC_MODAL_AUDIO_URL", "")
66
+ or os.environ.get("EPIC_MODAL_VOXCPM2_URL", "")
67
+ ),
68
  auth_token=os.environ.get("APP_MODAL_AUTH_TOKEN", ""),
69
  timeout_seconds=float(os.environ.get("APP_MODAL_TIMEOUT_SECONDS", "45") or 45),
70
  model_id=os.environ.get("APP_MODAL_MODEL_ID", ""),
 
225
  def _metadata_from_modal(result: dict[str, Any]) -> RuntimeMetadata:
226
  return RuntimeMetadata(
227
  lifecycle_stage=str(result.get("lifecycle_stage") or "testing"),
228
+ app_host="hf_space" if os.environ.get("SPACE_ID") else "local",
229
  model_runtime=str(result.get("model_runtime") or "modal"),
230
  model_backend=str(result.get("model_backend") or "modal_http"),
231
  inference_engine=str(result.get("inference_engine") or "unknown"),
epic_errands_v2/generation.py CHANGED
@@ -1,7 +1,10 @@
1
  from __future__ import annotations
2
 
 
3
  from datetime import datetime, timezone
4
  import hashlib
 
 
5
  from typing import Any
6
 
7
  from .contracts import EditableTextLayer, GeneratedGoal, GeneratedMedia
@@ -91,6 +94,141 @@ def build_generated_goal(
91
  }
92
 
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  def build_diy_state(
95
  theme_id: str,
96
  ordinary_goal: str,
@@ -139,6 +277,13 @@ def build_diy_state(
139
  }
140
 
141
 
 
 
 
 
 
 
 
142
  def _theme(value: str) -> str:
143
  return {"magical": "questbook"}.get(str(value or "questbook").lower(), str(value or "questbook").lower()) if str(value or "").lower() in THEMES or str(value or "").lower() == "magical" else "questbook"
144
 
 
1
  from __future__ import annotations
2
 
3
+ from dataclasses import asdict
4
  from datetime import datetime, timezone
5
  import hashlib
6
+ import os
7
+ import time
8
  from typing import Any
9
 
10
  from .contracts import EditableTextLayer, GeneratedGoal, GeneratedMedia
 
94
  }
95
 
96
 
97
+ def build_generated_goal_with_live_fallback(
98
+ ordinary_goal: str,
99
+ ui_theme_id: str = "questbook",
100
+ selected_generation_reference_ids: list[str] | None = None,
101
+ *,
102
+ audio_used_parent_reference: bool = False,
103
+ ) -> dict[str, Any]:
104
+ if not _live_generation_enabled():
105
+ return build_generated_goal(
106
+ ordinary_goal,
107
+ ui_theme_id,
108
+ selected_generation_reference_ids=selected_generation_reference_ids,
109
+ audio_used_parent_reference=audio_used_parent_reference,
110
+ )
111
+
112
+ try:
113
+ return _build_modal_generated_goal(
114
+ ordinary_goal,
115
+ ui_theme_id,
116
+ selected_generation_reference_ids=selected_generation_reference_ids,
117
+ audio_used_parent_reference=audio_used_parent_reference,
118
+ )
119
+ except Exception as exc: # noqa: BLE001 - user-facing fallback keeps hosted app usable.
120
+ fallback = build_generated_goal(
121
+ ordinary_goal,
122
+ ui_theme_id,
123
+ selected_generation_reference_ids=selected_generation_reference_ids,
124
+ audio_used_parent_reference=audio_used_parent_reference,
125
+ )
126
+ fallback["provenance"] = {
127
+ **fallback.get("provenance", {}),
128
+ "backend_transport": "modal_http",
129
+ "fallback_reason": f"{exc.__class__.__name__}: {exc}",
130
+ "fallback_used": True,
131
+ }
132
+ return fallback
133
+
134
+
135
+ def _build_modal_generated_goal(
136
+ ordinary_goal: str,
137
+ ui_theme_id: str,
138
+ selected_generation_reference_ids: list[str] | None = None,
139
+ *,
140
+ audio_used_parent_reference: bool = False,
141
+ ) -> dict[str, Any]:
142
+ from diy_lab.epic_errands_diy_workflow.contracts import AudioRequest, ImageRequest, PlainGoalInput
143
+ from diy_lab.epic_errands_diy_workflow.modal_client import ModalClient
144
+
145
+ theme = _theme(ui_theme_id)
146
+ goal_text = ordinary_goal.strip() or "Finish a helpful errand"
147
+ reference_signature = ",".join(selected_generation_reference_ids or [])
148
+ goal_id = f"goal-{hashlib.sha1(f'{goal_text}:{reference_signature}'.encode('utf-8')).hexdigest()[:10]}"
149
+ started = time.monotonic()
150
+ client = ModalClient()
151
+ if client.config.dry_run:
152
+ raise RuntimeError("Live generation is enabled, but Modal client is still in dry-run mode.")
153
+
154
+ goal = PlainGoalInput(goal_id=goal_id, ordinary_goal=goal_text, theme=theme)
155
+ text = client.generate_text(goal)
156
+ card_text = {
157
+ "title": text.title,
158
+ "narration": text.narration,
159
+ "reward_label": text.reward_label,
160
+ }
161
+ image = client.generate_image(
162
+ ImageRequest(
163
+ goal_id=goal_id,
164
+ ordinary_goal=goal_text,
165
+ theme=theme,
166
+ card_text=card_text,
167
+ prompt="",
168
+ width=768,
169
+ height=768,
170
+ seed=26000 + len(goal_text),
171
+ )
172
+ )
173
+ audio = client.generate_audio(
174
+ AudioRequest(
175
+ goal_id=goal_id,
176
+ ordinary_goal=goal_text,
177
+ theme=theme,
178
+ card_text=card_text,
179
+ spoken_text=f"{text.title}. {text.narration}",
180
+ audio_prompt="",
181
+ )
182
+ )
183
+
184
+ if not image.artifact_uri.startswith("data:image/"):
185
+ raise RuntimeError("Modal image response did not include image data.")
186
+ if not audio.artifact_uri.startswith("data:audio/"):
187
+ raise RuntimeError("Modal audio response did not include audio data.")
188
+
189
+ now = datetime.now(timezone.utc).isoformat()
190
+ runtime_steps = {
191
+ "text": asdict(text.runtime_metadata),
192
+ "image": asdict(image.runtime_metadata),
193
+ "audio": asdict(audio.runtime_metadata),
194
+ }
195
+ fallback_used = any(bool(step.get("fallback_used")) for step in runtime_steps.values())
196
+ generated = GeneratedGoal(
197
+ id=goal_id,
198
+ ordinary_goal=goal_text,
199
+ theme_id_at_creation=theme,
200
+ selected_generation_reference_ids=selected_generation_reference_ids or [],
201
+ generated_title=text.title,
202
+ generated_narration=text.narration,
203
+ generated_reward_label=text.reward_label,
204
+ overlay_text=EditableTextLayer(text=goal_text, theme_style_id=theme),
205
+ media=GeneratedMedia(
206
+ image_asset_ref=image.artifact_uri,
207
+ audio_asset_ref=audio.artifact_uri,
208
+ audio_used_parent_reference=audio_used_parent_reference,
209
+ ),
210
+ provenance={
211
+ **quality_provenance(trace_id=f"hosted-v2-modal-{goal_id}", fallback_used=fallback_used),
212
+ "app_host": "hf_space" if os.environ.get("SPACE_ID") else "local",
213
+ "model_runtime": "modal",
214
+ "model_backend": "modal_http",
215
+ "backend_transport": "modal_http",
216
+ "api_name": "epic_generate_goal",
217
+ "latency_ms": round((time.monotonic() - started) * 1000),
218
+ "modal_runtime_steps": runtime_steps,
219
+ },
220
+ created_at=now,
221
+ )
222
+ return {
223
+ **generated.__dict__,
224
+ "overlay_text": generated.overlay_text.__dict__,
225
+ "media": generated.media.__dict__,
226
+ "kid_completion_state": "not_started",
227
+ "parent_reward_state": "not_reviewed",
228
+ "updated_at": now,
229
+ }
230
+
231
+
232
  def build_diy_state(
233
  theme_id: str,
234
  ordinary_goal: str,
 
277
  }
278
 
279
 
280
+ def _live_generation_enabled() -> bool:
281
+ return (
282
+ os.environ.get("EPIC_ENABLE_LIVE_GENERATION", "").strip().lower() in {"1", "true", "yes"}
283
+ or os.environ.get("APP_DRY_RUN", "true").strip().lower() in {"0", "false", "no"}
284
+ )
285
+
286
+
287
  def _theme(value: str) -> str:
288
  return {"magical": "questbook"}.get(str(value or "questbook").lower(), str(value or "questbook").lower()) if str(value or "").lower() in THEMES or str(value or "").lower() == "magical" else "questbook"
289
 
frontend/scripts/app.js CHANGED
@@ -176,6 +176,7 @@
176
  }
177
 
178
  function asset(path) {
 
179
  if (ASSET_MANIFEST[path]) return ASSET_MANIFEST[path];
180
  return `${ASSET_BASE}${path}`;
181
  }
 
176
  }
177
 
178
  function asset(path) {
179
+ if (/^(data:|https?:|blob:)/.test(String(path || ""))) return path;
180
  if (ASSET_MANIFEST[path]) return ASSET_MANIFEST[path];
181
  return `${ASSET_BASE}${path}`;
182
  }