""" UI helpers โ€” shared functions across Gradio tabs. Keeps `app.py` focused on layout, not formatting logic. """ from __future__ import annotations from . import quota, workspace def get_status_bar() -> str: """Build the global status bar shown at the bottom of every tab. Markdown-formatted single line with quota + workspace info. """ state = workspace.get_state() parts = [ quota.format_status(), f"๐Ÿ“ Workspace: {workspace.current_size_mb():.1f} MB", f"๐Ÿ“ฆ Exports: {workspace.export_count()}", ] if state.model_used: parts.append(f"๐Ÿ”ง Last: {state.model_used}") return " ยท ".join(parts) def get_asset_summary() -> str: """Multi-line summary of what's currently in `workspace/current/`. Used in the viewer panel to show pipeline progress. """ state = workspace.get_state() lines = [f"**Asset:** `{state.asset_name}`"] if state.face_count: lines.append(f"- Faces: {state.face_count:,}") lines.append(f"- Vertices: {state.vertex_count:,}") stages = [] if state.high_poly_glb: stages.append("โœ“ Generated") if state.cleaned_glb: stages.append("โœ“ Cleaned") if state.low_poly_glb: stages.append("โœ“ Decimated") if state.unwrapped_glb: stages.append("โœ“ UV unwrapped") if state.normal_dx_png or state.normal_gl_png: stages.append("โœ“ Normal baked") if state.albedo_png: stages.append("โœ“ Albedo baked") if state.orm_png or (state.roughness_png and state.metallic_png): stages.append("โœ“ PBR maps") if state.lod_glbs: stages.append(f"โœ“ LODs ({len(state.lod_glbs)})") if state.collision_glb: stages.append("โœ“ Collision") if state.rigged_glb or state.rigged_fbx: stages.append("โœ“ Rigged") if stages: lines.append("") lines.append("**Progress:**") for s in stages: lines.append(f"- {s}") else: lines.append("") lines.append("*No asset loaded yet. Start at the Generate tab.*") return "\n".join(lines) def get_viewer_model_path() -> str | None: """Pick the best GLB to show in the 3D viewer. Reads the filesystem directly so it works across the ZeroGPU subprocess boundary (the in-memory state written inside @spaces.GPU is invisible to the parent Gradio process). """ from .workspace import CURRENT # Order: most processed โ†’ least processed candidates = [ CURRENT / "rigged.glb", CURRENT / "scaled.glb", CURRENT / "pivoted.glb", CURRENT / "lods" / "LOD0.glb", CURRENT / "unwrapped.glb", CURRENT / "low_poly.glb", CURRENT / "cleaned.glb", CURRENT / "repaired.glb", CURRENT / "raw_gen.glb", CURRENT / "high_poly.glb", ] for path in candidates: if path.exists(): return str(path) return None def quota_warning(operation: str) -> str: """Generate a warning if an operation would exceed the daily quota. Returns an empty string if the operation fits comfortably. """ estimated = quota.estimate(operation) state = quota.get_state() remaining = state.remaining_seconds() if estimated > remaining: overage = estimated - remaining cost = overage * quota.OVERAGE_RATE_PER_SECOND return ( f"โš ๏ธ **Quota warning:** This will use ~{estimated}s but you only " f"have {remaining:.0f}s left today. " f"Overage cost: ~${cost:.2f}" ) elif estimated > remaining * 0.5: return ( f"โ„น๏ธ Estimated GPU time: ~{estimated}s " f"({remaining:.0f}s remaining today)" ) return ""