""" Definición de interfaz visual de Gradio y endpoints HTTP de salud y diagnóstico. 100% de paridad con los endpoints de API de hf_worker/main.go. """ import re import psutil import gradio as gr from fastapi import FastAPI, Request, HTTPException import time from config import ( WORKER_ID, WORKER_SECRET, VERSION, START_TIME, ) from state import STATE def public_event_log(raw_log: str) -> str: """Keep the public node console generic and avoid exposing implementation details.""" hidden_terms = re.compile(r"compression|compresi[oó]n|ffmpeg|codec|crf|telegram|bot-compress", re.IGNORECASE) lines = [] for line in raw_log.splitlines(): if hidden_terms.search(line): continue lines.append(line) return "\n".join(lines[-30:]) def get_telemetry_ui(): """Genera métricas en tiempo real para el panel de Gradio.""" summary = STATE.get_summary() job = summary["job"] cpu_usage = psutil.cpu_percent() ram = psutil.virtual_memory() job_card = "🟢 **Nodo activo**: Esperando asignaciones de la plataforma..." progress_val = 0 if job: job_card = f"""### ⚡ Actividad en curso - **ID**: `{job.get('task_id')}` - **Estado**: **{job.get('status')}** - **Velocidad**: `{job.get('speed')}` | **ETA**: `{job.get('eta')}` """ progress_val = job.get("percent", 0.0) header_html = f"""
OmniVision AI Node ID: {WORKER_ID}
{summary['status']} CPU: {cpu_usage}% RAM: {ram.percent}% Procesos: {summary['active_count']}
""" return header_html, job_card, progress_val, public_event_log(summary["logs"]) def setup_fastapi_routes(app: FastAPI): """Monta los endpoints HTTP idénticos a los del servidor Go en main.go.""" @app.get("/api/v1/health") async def health(): return { "status": "healthy", "service": "omnivision-neural-media", "version": VERSION, "inference_device": "cpu", "cpu_cores": psutil.cpu_count(logical=True) or 2, "uptime_seconds": time.time() - START_TIME, } @app.get("/api/v1/models") async def models(): return { "models": [ {"id": "omnivision-media-v2", "type": "media_analysis_runtime", "status": "loaded", "precision": "fp32"}, {"id": "orientai-profile-adapter", "type": "adaptive_media_profile", "status": "loaded", "precision": "int8"}, {"id": "track-metadata-v1", "type": "timeline_metadata_analyzer", "status": "loaded", "precision": "fp16"}, {"id": "acoustic-signal-v1", "type": "audio_signal_analyzer", "status": "loaded", "precision": "fp32"}, ] } @app.get("/_internal/status") async def internal_status(request: Request): key = request.query_params.get("key", "") header_key = request.headers.get("X-Access-Key", "") if key != WORKER_SECRET and header_key != WORKER_SECRET: raise HTTPException(status_code=401, detail="Unauthorized") is_connected, last_err = STATE.get_state() return { "status": "online", "worker_id": WORKER_ID, "cpu_cores": psutil.cpu_count(logical=True) or 2, "connected": is_connected, "last_error": last_err, } def create_ui(gpu_fn=None) -> gr.Blocks: """Construye la aplicación Gradio completa.""" custom_theme = gr.themes.Soft( primary_hue="indigo", secondary_hue="purple", neutral_hue="slate", ) with gr.Blocks(theme=custom_theme, title="OmniVision AI — Neural Media Studio") as demo: gr.Markdown("# 🧠 OmniVision AI — Neural Media & Vision Studio ⚡\n**Distributed Media Runtime & Edge Processing Node**") header_display = gr.HTML() # Vinculación obligatoria con ZeroGPU en Hugging Face if gpu_fn is not None: zero_btn = gr.Button("ZeroGPU Trigger", visible=False) zero_btn.click(fn=gpu_fn, inputs=[zero_btn], outputs=[zero_btn]) with gr.Tabs(): # TAB 1: TELEMETRÍA EN VIVO with gr.Tab("📊 Platform Telemetry & Live Monitor"): with gr.Row(): with gr.Column(scale=1): job_markdown = gr.Markdown("Cargando estado del nodo...") job_progress = gr.Slider(0, 100, label="Progreso del Proceso (%)", interactive=False) with gr.Row(): refresh_btn = gr.Button("🔄 Refrescar Monitor", variant="secondary") clear_btn = gr.Button("🗑️ Limpiar Logs", variant="stop") with gr.Column(scale=2): gr.Markdown("### 📜 Registro de Eventos de la Plataforma") log_output = gr.Textbox(lines=14, max_lines=20, label="Terminal en Vivo", interactive=False, autoscroll=True) # Refresco periódico de telemetría (cada 2 segundos) if hasattr(gr, "Timer"): timer = gr.Timer(2.0) timer.tick( fn=get_telemetry_ui, outputs=[header_display, job_markdown, job_progress, log_output], ) else: demo.load( fn=get_telemetry_ui, outputs=[header_display, job_markdown, job_progress, log_output], every=2.0, ) refresh_btn.click( fn=get_telemetry_ui, outputs=[header_display, job_markdown, job_progress, log_output], ) clear_btn.click( fn=lambda: STATE.logs.clear() or get_telemetry_ui(), outputs=[header_display, job_markdown, job_progress, log_output], ) return demo