Spaces:
Running on Zero
Running on Zero
feat: Upgrade Gradio UI to match Streamlit dark theme, styled metric cards, and narrative html
Browse files
app.py
CHANGED
|
@@ -25,6 +25,83 @@ import gradio as gr
|
|
| 25 |
import pandas as pd
|
| 26 |
import plotly.graph_objects as go
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
from space_backend import (
|
| 29 |
PREDICT_MODEL_NAME,
|
| 30 |
build_pipeline_overview,
|
|
@@ -190,7 +267,7 @@ def make_artifact_figure(artifacts):
|
|
| 190 |
return figure
|
| 191 |
|
| 192 |
|
| 193 |
-
def
|
| 194 |
overview = (pipeline_payload or {}).get("overview") or build_pipeline_overview(badas_result, reason_result)
|
| 195 |
incident_bg, incident_accent, incident_label = badge_palette(reason_result.get("incident_type"), "incident")
|
| 196 |
severity_bg, severity_accent, severity_label = badge_palette(reason_result.get("severity_label"), "severity")
|
|
@@ -213,7 +290,7 @@ def build_summary_markdown(pipeline_payload, badas_result, reason_result, predic
|
|
| 213 |
"""
|
| 214 |
|
| 215 |
|
| 216 |
-
def
|
| 217 |
if not reason_result:
|
| 218 |
return "Run BADAS + Reason to populate the narrative panel."
|
| 219 |
validation = (reason_result or {}).get("validation") or {}
|
|
@@ -285,7 +362,7 @@ def build_outputs(pipeline_payload, logs, preview_video_path, predict_payload=No
|
|
| 285 |
return (
|
| 286 |
"Pipeline completed." if pipeline_payload else "No pipeline payload available.",
|
| 287 |
logs,
|
| 288 |
-
|
| 289 |
pipeline_payload,
|
| 290 |
preview_video_path,
|
| 291 |
artifacts.get("extracted_clip"),
|
|
@@ -300,7 +377,7 @@ def build_outputs(pipeline_payload, logs, preview_video_path, predict_payload=No
|
|
| 300 |
make_reason_coverage_heatmap(reason_result),
|
| 301 |
make_risk_gauge(reason_result),
|
| 302 |
make_artifact_figure(artifacts),
|
| 303 |
-
|
| 304 |
badas_result,
|
| 305 |
reason_result,
|
| 306 |
pipeline_payload,
|
|
@@ -363,7 +440,7 @@ def safe_warmup_message():
|
|
| 363 |
return traceback.format_exc()
|
| 364 |
|
| 365 |
|
| 366 |
-
with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
| 367 |
pipeline_state = gr.State(None)
|
| 368 |
input_video_state = gr.State(None)
|
| 369 |
|
|
@@ -395,9 +472,9 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
|
| 395 |
with gr.Tabs():
|
| 396 |
with gr.Tab("Run Outputs"):
|
| 397 |
status_markdown = gr.Markdown("Ready.")
|
| 398 |
-
|
| 399 |
pipeline_logs = gr.Textbox(label="Pipeline logs", lines=18)
|
| 400 |
-
|
| 401 |
with gr.Row():
|
| 402 |
extracted_clip = gr.Video(label="BADAS-focused clip")
|
| 403 |
conditioning_clip = gr.Video(label="Predict conditioning clip")
|
|
@@ -453,7 +530,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
|
| 453 |
outputs=[
|
| 454 |
status_markdown,
|
| 455 |
pipeline_logs,
|
| 456 |
-
|
| 457 |
pipeline_state,
|
| 458 |
preview_video,
|
| 459 |
extracted_clip,
|
|
@@ -468,7 +545,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
|
| 468 |
reason_heatmap,
|
| 469 |
risk_gauge,
|
| 470 |
artifact_plot,
|
| 471 |
-
|
| 472 |
badas_json,
|
| 473 |
reason_json,
|
| 474 |
pipeline_json,
|
|
@@ -484,7 +561,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
|
| 484 |
outputs=[
|
| 485 |
status_markdown,
|
| 486 |
pipeline_logs,
|
| 487 |
-
|
| 488 |
pipeline_state,
|
| 489 |
preview_video,
|
| 490 |
extracted_clip,
|
|
@@ -499,7 +576,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Cosmos Sentinel") as demo:
|
|
| 499 |
reason_heatmap,
|
| 500 |
risk_gauge,
|
| 501 |
artifact_plot,
|
| 502 |
-
|
| 503 |
badas_json,
|
| 504 |
reason_json,
|
| 505 |
pipeline_json,
|
|
|
|
| 25 |
import pandas as pd
|
| 26 |
import plotly.graph_objects as go
|
| 27 |
|
| 28 |
+
|
| 29 |
+
def format_html_cards(badas_result, reason_result):
|
| 30 |
+
if not badas_result and not reason_result:
|
| 31 |
+
return "<div style='color: #94a3b8; font-style: italic;'>Run the cached sample or upload a video to execute the pipeline.</div>"
|
| 32 |
+
|
| 33 |
+
collision_triggered = bool((badas_result or {}).get("collision_detected"))
|
| 34 |
+
gate_color = "#22c55e" if collision_triggered else "#64748b"
|
| 35 |
+
gate_text = "Triggered" if collision_triggered else "Watching"
|
| 36 |
+
|
| 37 |
+
incident = ((reason_result or {}).get("incident_type") or "unclear").lower()
|
| 38 |
+
incident_colors = {
|
| 39 |
+
"collision": ("#7f1d1d", "#ef4444"),
|
| 40 |
+
"near_miss": ("#7c2d12", "#f97316"),
|
| 41 |
+
"hazard": ("#713f12", "#f59e0b"),
|
| 42 |
+
}
|
| 43 |
+
inc_bg, inc_accent = incident_colors.get(incident, ("#1e293b", "#94a3b8"))
|
| 44 |
+
|
| 45 |
+
severity = str((reason_result or {}).get("severity_label") or "unknown").lower()
|
| 46 |
+
severity_colors = {
|
| 47 |
+
"1": ("#14532d", "#22c55e"),
|
| 48 |
+
"2": ("#713f12", "#f59e0b"),
|
| 49 |
+
"3": ("#7c2d12", "#f97316"),
|
| 50 |
+
"4": ("#7f1d1d", "#ef4444"),
|
| 51 |
+
"5": ("#4c0519", "#e11d48"),
|
| 52 |
+
}
|
| 53 |
+
sev_bg, sev_accent = severity_colors.get(severity, ("#1e293b", "#94a3b8"))
|
| 54 |
+
|
| 55 |
+
risk_score = (reason_result or {}).get("risk_score", 0)
|
| 56 |
+
|
| 57 |
+
cards_html = f"""
|
| 58 |
+
<div style="display: flex; gap: 1rem; flex-wrap: wrap; margin-bottom: 1rem; font-family: sans-serif;">
|
| 59 |
+
<div style="flex: 1; min-width: 200px; background: #0f172a; border-left: 4px solid {gate_color}; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
|
| 60 |
+
<div style="color: #94a3b8; font-size: 0.875rem; text-transform: uppercase; font-weight: 600;">Collision Gate</div>
|
| 61 |
+
<div style="color: {gate_color}; font-size: 1.5rem; font-weight: 700; margin-top: 0.25rem;">{gate_text}</div>
|
| 62 |
+
<div style="color: #64748b; font-size: 0.75rem; margin-top: 0.25rem;">BADAS V-JEPA2</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div style="flex: 1; min-width: 200px; background: {inc_bg}; border-left: 4px solid {inc_accent}; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
|
| 66 |
+
<div style="color: #94a3b8; font-size: 0.875rem; text-transform: uppercase; font-weight: 600;">Incident</div>
|
| 67 |
+
<div style="color: #f8fafc; font-size: 1.5rem; font-weight: 700; margin-top: 0.25rem; text-transform: capitalize;">{incident.replace("_", " ")}</div>
|
| 68 |
+
<div style="color: #cbd5e1; font-size: 0.75rem; margin-top: 0.25rem;">Cosmos Reason 2</div>
|
| 69 |
+
</div>
|
| 70 |
+
|
| 71 |
+
<div style="flex: 1; min-width: 200px; background: {sev_bg}; border-left: 4px solid {sev_accent}; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
|
| 72 |
+
<div style="color: #94a3b8; font-size: 0.875rem; text-transform: uppercase; font-weight: 600;">Severity</div>
|
| 73 |
+
<div style="color: #f8fafc; font-size: 1.5rem; font-weight: 700; margin-top: 0.25rem;">{severity.upper()}</div>
|
| 74 |
+
<div style="color: #cbd5e1; font-size: 0.75rem; margin-top: 0.25rem;">Scale 1-5</div>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div style="flex: 1; min-width: 200px; background: #0f172a; border-left: 4px solid #3b82f6; padding: 1rem; border-radius: 0.5rem; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
|
| 78 |
+
<div style="color: #94a3b8; font-size: 0.875rem; text-transform: uppercase; font-weight: 600;">Risk Score</div>
|
| 79 |
+
<div style="color: #3b82f6; font-size: 1.5rem; font-weight: 700; margin-top: 0.25rem;">{risk_score}/10</div>
|
| 80 |
+
<div style="color: #64748b; font-size: 0.75rem; margin-top: 0.25rem;">Overall hazard rating</div>
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
"""
|
| 84 |
+
return cards_html
|
| 85 |
+
|
| 86 |
+
def format_reason_html(reason_result):
|
| 87 |
+
narrative = (reason_result or {}).get("narrative", "")
|
| 88 |
+
reasoning = (reason_result or {}).get("reasoning", "")
|
| 89 |
+
|
| 90 |
+
if not narrative:
|
| 91 |
+
return "<div style='color: #94a3b8; font-style: italic;'>Run BADAS + Reason to populate the narrative panel.</div>"
|
| 92 |
+
|
| 93 |
+
html = f"""
|
| 94 |
+
<div style="background: rgba(15,23,42,0.65); border: 1px solid rgba(51,65,85,0.8); border-radius: 0.5rem; padding: 1.25rem; margin-top: 1rem; font-family: sans-serif;">
|
| 95 |
+
<h3 style="color: #e2e8f0; margin-top: 0; margin-bottom: 0.5rem; font-size: 1.125rem;">Cosmos Reason 2 Narrative</h3>
|
| 96 |
+
<p style="color: #cbd5e1; line-height: 1.6; margin: 0;">{narrative}</p>
|
| 97 |
+
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid rgba(51,65,85,0.5);">
|
| 98 |
+
<h4 style="color: #94a3b8; margin-top: 0; margin-bottom: 0.5rem; font-size: 0.875rem; text-transform: uppercase;">Reasoning Trace</h4>
|
| 99 |
+
<p style="color: #94a3b8; font-family: monospace; font-size: 0.875rem; line-height: 1.5; margin: 0; white-space: pre-wrap;">{reasoning}</p>
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
"""
|
| 103 |
+
return html
|
| 104 |
+
|
| 105 |
from space_backend import (
|
| 106 |
PREDICT_MODEL_NAME,
|
| 107 |
build_pipeline_overview,
|
|
|
|
| 267 |
return figure
|
| 268 |
|
| 269 |
|
| 270 |
+
def format_html_cards(badas_result, reason_result):
|
| 271 |
overview = (pipeline_payload or {}).get("overview") or build_pipeline_overview(badas_result, reason_result)
|
| 272 |
incident_bg, incident_accent, incident_label = badge_palette(reason_result.get("incident_type"), "incident")
|
| 273 |
severity_bg, severity_accent, severity_label = badge_palette(reason_result.get("severity_label"), "severity")
|
|
|
|
| 290 |
"""
|
| 291 |
|
| 292 |
|
| 293 |
+
def format_reason_html(reason_result):
|
| 294 |
if not reason_result:
|
| 295 |
return "Run BADAS + Reason to populate the narrative panel."
|
| 296 |
validation = (reason_result or {}).get("validation") or {}
|
|
|
|
| 362 |
return (
|
| 363 |
"Pipeline completed." if pipeline_payload else "No pipeline payload available.",
|
| 364 |
logs,
|
| 365 |
+
format_html_cards(badas_result, reason_result),
|
| 366 |
pipeline_payload,
|
| 367 |
preview_video_path,
|
| 368 |
artifacts.get("extracted_clip"),
|
|
|
|
| 377 |
make_reason_coverage_heatmap(reason_result),
|
| 378 |
make_risk_gauge(reason_result),
|
| 379 |
make_artifact_figure(artifacts),
|
| 380 |
+
format_reason_html(reason_result),
|
| 381 |
badas_result,
|
| 382 |
reason_result,
|
| 383 |
pipeline_payload,
|
|
|
|
| 440 |
return traceback.format_exc()
|
| 441 |
|
| 442 |
|
| 443 |
+
with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue").set(body_background_fill="#0f172a", body_text_color="#f1f5f9"), title="Cosmos Sentinel") as demo:
|
| 444 |
pipeline_state = gr.State(None)
|
| 445 |
input_video_state = gr.State(None)
|
| 446 |
|
|
|
|
| 472 |
with gr.Tabs():
|
| 473 |
with gr.Tab("Run Outputs"):
|
| 474 |
status_markdown = gr.Markdown("Ready.")
|
| 475 |
+
summary_html = gr.HTML("<div style='color: #94a3b8; font-style: italic;'>Run the cached sample or upload a video to execute the pipeline.</div>")
|
| 476 |
pipeline_logs = gr.Textbox(label="Pipeline logs", lines=18)
|
| 477 |
+
reason_html = gr.HTML("<div style='color: #94a3b8; font-style: italic;'>Run BADAS + Reason to populate the narrative panel.</div>")
|
| 478 |
with gr.Row():
|
| 479 |
extracted_clip = gr.Video(label="BADAS-focused clip")
|
| 480 |
conditioning_clip = gr.Video(label="Predict conditioning clip")
|
|
|
|
| 530 |
outputs=[
|
| 531 |
status_markdown,
|
| 532 |
pipeline_logs,
|
| 533 |
+
summary_html,
|
| 534 |
pipeline_state,
|
| 535 |
preview_video,
|
| 536 |
extracted_clip,
|
|
|
|
| 545 |
reason_heatmap,
|
| 546 |
risk_gauge,
|
| 547 |
artifact_plot,
|
| 548 |
+
reason_html,
|
| 549 |
badas_json,
|
| 550 |
reason_json,
|
| 551 |
pipeline_json,
|
|
|
|
| 561 |
outputs=[
|
| 562 |
status_markdown,
|
| 563 |
pipeline_logs,
|
| 564 |
+
summary_html,
|
| 565 |
pipeline_state,
|
| 566 |
preview_video,
|
| 567 |
extracted_clip,
|
|
|
|
| 576 |
reason_heatmap,
|
| 577 |
risk_gauge,
|
| 578 |
artifact_plot,
|
| 579 |
+
reason_html,
|
| 580 |
badas_json,
|
| 581 |
reason_json,
|
| 582 |
pipeline_json,
|