Ryukijano commited on
Commit
5d4f847
·
1 Parent(s): 5de6307

feat: Upgrade Gradio UI to match Streamlit dark theme, styled metric cards, and narrative html

Browse files
Files changed (1) hide show
  1. app.py +88 -11
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 build_summary_markdown(pipeline_payload, badas_result, reason_result, predict_payload):
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 build_reason_markdown(reason_result):
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
- build_summary_markdown(pipeline_payload, badas_result, reason_result, predict_payload),
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
- build_reason_markdown(reason_result),
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
- summary_markdown = gr.Markdown("Run the cached sample or upload a video to execute the pipeline.")
399
  pipeline_logs = gr.Textbox(label="Pipeline logs", lines=18)
400
- reason_markdown = gr.Markdown("Run BADAS + Reason to populate the narrative panel.")
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
- summary_markdown,
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
- reason_markdown,
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
- summary_markdown,
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
- reason_markdown,
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,