""" Standalone non-destructive patch for DVNC.AI Hugging Face Space. Purpose - Fix Bug 1: hover/focus flip interaction makes the Flip Insight button unreliable. - Fix Bug 2: brittle Gradio element ID lookup for route swap payload/apply controls. - Fix Bug 3: route swap leaves timeline stale/blank by rebuilding/preserving the timeline output. Design - Leaves the existing codebase untouched. - Intended to be imported and applied from a separate runner or notebook. - Uses monkey-patching where possible. Usage example import app as dvnc_app import dvnc_flip_insight_patch as patch patch.apply_patch(dvnc_app) If you want to keep the current app.py completely untouched, create a tiny launcher file that imports the original app module and then calls apply_patch(dvnc_app). """ from __future__ import annotations import re from typing import Any, Callable PATCH_SCRIPT = r""" """ PATCH_STYLE = r""" """ def _inject_assets(head: str) -> str: if "__dvncFlipPatchLoaded" in head: return head return head + "\n" + PATCH_STYLE + "\n" + PATCH_SCRIPT def _patch_head(module: Any) -> None: for attr in ("HEAD", "head", "CUSTOM_HEAD"): if hasattr(module, attr): value = getattr(module, attr) if isinstance(value, str): setattr(module, attr, _inject_assets(value)) return def _patch_cards_builder(module: Any) -> None: if not hasattr(module, "build_cards_html"): return original = module.build_cards_html def wrapped(*args, **kwargs): out = original(*args, **kwargs) if not isinstance(out, str): return out if "candidate-card" not in out: return out out = re.sub( r'( Any: if route_state is None: return None builder = getattr(module, "build_agent_route_cards_html", None) if not callable(builder): return None try: variants = route_state.get("variants") if isinstance(route_state, dict) else None active_idx = route_state.get("active_variant", 0) if isinstance(route_state, dict) else 0 if not variants or active_idx >= len(variants): return None variant = variants[active_idx] or {} steps = variant.get("steps") or variant.get("route") or [] if not steps: return None normalized = [] for i, step in enumerate(steps): if isinstance(step, dict): normalized.append( { "step": step.get("step", i + 1), "agent": step.get("agent", f"Step {i+1}"), "tag": step.get("tag", step.get("mode", "")), "summary": step.get("summary", step.get("description", "")), } ) else: normalized.append( { "step": i + 1, "agent": f"Step {i+1}", "tag": "", "summary": str(step), } ) return builder(normalized) except Exception: return None def _patch_apply_route_swap(module: Any) -> None: if not hasattr(module, "apply_route_swap"): return original = module.apply_route_swap def wrapped(*args, **kwargs): result = original(*args, **kwargs) if not isinstance(result, tuple): return result if len(result) == 5: chat_html, connectome_html, timeline_value, hypothesis_md, route_state = result timeline_rebuilt = _build_timeline_from_state(module, route_state) if timeline_rebuilt is not None: gr = getattr(module, "gr", None) if gr is not None and hasattr(gr, "update"): timeline_value = gr.update(value=timeline_rebuilt) else: timeline_value = timeline_rebuilt return chat_html, connectome_html, timeline_value, hypothesis_md, route_state return result module.apply_route_swap = wrapped def _patch_rendered_html(module: Any) -> None: for attr in ("DISCOVERY_CARD_CSS",): if hasattr(module, attr) and isinstance(getattr(module, attr), str): setattr(module, attr, getattr(module, attr) + "\n" + PATCH_STYLE) def apply_patch(module: Any) -> Any: _patch_head(module) _patch_rendered_html(module) _patch_cards_builder(module) _patch_apply_route_swap(module) return module