import logging # Wikimedia (and several other image CDNs) reject the default # "Python-urllib/3.x" User-Agent with HTTP 403 Forbidden. urllib.request # .urlretrieve() cannot carry headers, so every image fetch below goes through # this helper instead — this was the real cause of SF3D model generation # failing with {"detail":"HTTP Error 403: Forbidden"}: the Wikipedia *API* call # set a User-Agent, but the follow-up image *download* did not. IMAGE_FETCH_UA = "JARVIS_OMEGA/1.0 (+https://huggingface.co/spaces/Jarvis2345/jarvis-cloud)" def _download_image(url: str, dest_path: str, timeout: int = 30) -> str: """Download `url` to `dest_path` with a real User-Agent. Returns dest_path.""" import urllib.request req = urllib.request.Request(url, headers={"User-Agent": IMAGE_FETCH_UA}) with urllib.request.urlopen(req, timeout=timeout) as resp, open(dest_path, "wb") as fh: fh.write(resp.read()) return dest_path async def xr_anchor_tool(action: str, anchor_id: str = "", *args, **kwargs): """ Control or query XR spatial anchors. Action: 'save', 'get', 'clear' """ from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "xr:anchor", "payload": {"action": action, "anchor_id": anchor_id} }) return f"Broadcasted XR anchor command: {action} on {anchor_id}" async def gesture_context_tool(gesture: str, *args, **kwargs): """ Fake/inject an XR gesture for testing or context (e.g. 'pinch', 'swipe'). """ from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": f"gesture-{gesture}", "payload": {"source": "jarvis"} }) return f"Injected simulated gesture: {gesture}" async def ar_control_tool(action: str, payload: str = "", *args, **kwargs): """ Control AR states globally (e.g. toggle AR, spawn model, enter vr, clear scene). """ from backend.ws.agent_ws import ws_manager await ws_manager.broadcast({ "event": "ar:control", "payload": {"action": action, "data": payload} }) return f"Dispatched AR control command: {action} with payload {payload}" class StableFast3DNotProvisionedError(Exception): pass async def is_exe_sidecar_reachable() -> bool: """Mock check if EXE sidecar is reachable.""" return True async def get_inference_target() -> str: if await is_exe_sidecar_reachable(): return "exe_local_gpu" else: return "hf_space_cpu_fallback" async def find_known_image(description: str) -> str | None: """Tier 1: Check SQLite then ChromaDB for a known prior 3D generation.""" # Fast path: SQLite lookup from backend.memory import database row = await database.lookup_model_description(description) if row: return row["model_path"] # Semantic match: ChromaDB lookup from backend.memory.episodic_memory import EpisodicMemory em = EpisodicMemory("storage/chroma_db") similar = await em.query_similar_model(description, threshold=0.85) if similar: return similar["model_path"] return None async def search_web_for_image(description: str) -> str | None: """Tier 2: Search the web (Wikimedia Commons) for a matching image.""" import logging import urllib.parse import urllib.request import json import tempfile import asyncio logging.info(f"Tier 2: Searching web for image matching: {description}") def _search(): # Using Wikipedia/Wikimedia API to find an open-source image url = f"https://en.wikipedia.org/w/api.php?action=query&prop=pageimages&format=json&piprop=original&titles={urllib.parse.quote(description)}" req = urllib.request.Request(url, headers={'User-Agent': 'JARVIS_OMEGA/1.0'}) try: with urllib.request.urlopen(req, timeout=5) as resp: data = json.loads(resp.read().decode('utf-8')) pages = data.get("query", {}).get("pages", {}) for page_id, page_data in pages.items(): if "original" in page_data: img_url = page_data["original"]["source"] tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) tmp.close() # UA-bearing fetch: Wikimedia 403s the default urllib UA. return _download_image(img_url, tmp.name) except Exception as e: logging.error(f"Web image search failed: {e}") return None return await asyncio.to_thread(_search) async def pollinations_generate_image(description: str) -> str: """Tier 3: Generating AI image as ultimate fallback.""" import logging import urllib.parse import urllib.request import tempfile import asyncio logging.info(f"Tier 3: Generating AI image for: {description}") def _generate(): # Fallback AI image generator (using free Pollinations API as proxy for Gemini output generation) # Note: In production this would use Gemini/Imagen via Vertex AI. prompt = urllib.parse.quote(f"A high quality realistic 3D asset of {description}, isolated on white background, studio lighting") url = f"https://image.pollinations.ai/prompt/{prompt}?width=1024&height=1024&nologo=true" try: tmp = tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) tmp.close() # Pollinations renders on demand — allow a longer timeout, and send a # real User-Agent (bare urlretrieve 403s on several image CDNs). return _download_image(url, tmp.name, timeout=120) except Exception as e: logging.error(f"AI image generation failed: {e}") raise e return await asyncio.to_thread(_generate) async def log_model_generation(description: str, image_tier: str, model_path: str, persona: str, inference_target: str): from backend.db import mongodb from backend.memory import database from backend.memory.episodic_memory import EpisodicMemory em = EpisodicMemory("storage/chroma_db") import asyncio await asyncio.gather( mongodb.log_model_generation_mongo(description, image_tier, model_path, persona, inference_target), em.embed_model_description(description, model_path, persona), database.insert_model_generation(description, image_tier, model_path, persona, inference_target) ) async def push_model_to_ar_scene(model_path: str, description: str): try: from phone import ws_scene_bus patch = { "type": "patch", "patch": { "action": "spawn-generated-glb", "model_url": f"/static/models3d/{model_path.split('/')[-1]}" if '/' in model_path else f"/static/models3d/{model_path}", "model_name": description } } # S4: broadcast_to_clients is sync — awaiting it raised TypeError and the # spawn broadcast never reached the AR clients. Use the async variant. await ws_scene_bus.broadcast_to_clients_async(patch) except Exception as e: logging.error(f"Failed to push model to AR scene bus: {e}") async def spawn_3d_model(description: str, persona: str, use_cloud_fallback: bool = False) -> str: from backend.services.connectors import stable_fast_3d_local health = await stable_fast_3d_local.health_check() if not health["ready"]: raise StableFast3DNotProvisionedError("Stable Fast 3D model is not installed. Run scripts/provision_stable_fast_3d.sh first.") image_source_tier = "known" image = await find_known_image(description) if not image: image_source_tier = "web_search" image = await search_web_for_image(description) if not image: image_source_tier = "ai_generated" image = await pollinations_generate_image(description) model_path = await stable_fast_3d_local.image_to_3d_local(image, use_cloud_fallback) if model_path == "INTERACTIVE_PROMPT_REQUIRED": return "STATUS: LOCAL_MODEL_MISSING. Please ask the user: 'The local SF3D engine is not installed. Should I initiate the 10GB installation script, or route this through the Cloud API for now?' If they choose Cloud API, call this tool again with use_cloud_fallback=True." await push_model_to_ar_scene(model_path, description) # S4: determine_inference_target is synchronous — awaiting it raised TypeError # after every successful spawn, killing the Mongo/Chroma/SQLite logging below. target = stable_fast_3d_local.determine_inference_target() await log_model_generation(description, image_source_tier, model_path, persona, target) return model_path