feat(dashboard): página Ejecutar con trace timeline y violations rendering

- components/trace_view.render_trace: decision_path como timeline con duración.
- components/violation_view.render_violations: violaciones con badge de severidad.
- pages/2_▶️_Ejecutar.py: selector de agente, botones de escenarios pregrabados,
  textarea, invoke y render de status/output/violations/traza. Aviso si queda en
  awaiting_approval. (Se omite la variable muerta chosen_example del plan.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Juan
2026-05-11 12:42:12 +02:00
co-authored by Claude Opus 4.7
parent f2ef01d98d
commit ab21db2476
3 changed files with 122 additions and 0 deletions
@@ -0,0 +1,24 @@
"""Renderiza el decision_path como timeline."""
from __future__ import annotations
import streamlit as st
def render_trace(decision_path: list[dict]) -> None:
if not decision_path:
st.info("Aún no hay traza disponible.")
return
st.subheader("📜 Decision path")
for step in decision_path:
with st.container(border=True):
cols = st.columns([2, 1, 1])
with cols[0]:
st.markdown(f"**{step['step']}**")
with cols[1]:
st.caption(step.get("timestamp", ""))
with cols[2]:
st.metric("ms", step.get("duration_ms", 0), label_visibility="collapsed")
if step.get("detail"):
with st.expander("detalle"):
st.json(step["detail"])
@@ -0,0 +1,26 @@
"""Renderiza violaciones de guardrails con badges de severidad."""
from __future__ import annotations
import streamlit as st
_SEV_BADGE = {
"info": "🟦",
"warning": "🟧",
"block": "🟥",
}
def render_violations(violations: list[dict]) -> None:
if not violations:
st.success("Sin violaciones.")
return
st.subheader("🛡️ Violaciones de guardrails")
for v in violations:
with st.container(border=True):
badge = _SEV_BADGE.get(v["severity"], "")
st.markdown(
f"{badge} **{v['validator']}** — `{v['stage']}` — "
f"severity=`{v['severity']}` — blocked=`{v['blocked']}`"
)
st.caption(v["message"])