THANOS / ui.py
st3v3x's picture
Actualizar nodo OmniVision
d26c643 verified
Raw History Blame Contribute Delete
6.39 kB
"""
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"""
<div style="display:flex; justify-content:space-between; align-items:center; background:#1e1e2e; padding:15px; border-radius:10px; color:#cdd6f4; border:1px solid #313244;">
<div>
<span style="font-size:1.4em; font-weight:bold; color:#89b4fa;">OmniVision AI Node</span>
<span style="margin-left:12px; background:#45475a; padding:4px 10px; border-radius:6px; font-size:0.85em;">ID: {WORKER_ID}</span>
</div>
<div style="display:flex; gap:16px;">
<span>{summary['status']}</span>
<span>CPU: <b>{cpu_usage}%</b></span>
<span>RAM: <b>{ram.percent}%</b></span>
<span>Procesos: <b>{summary['active_count']}</b></span>
</div>
</div>
"""
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