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,72 @@
"""Página para invocar un agente y visualizar la traza completa."""
from __future__ import annotations
from pathlib import Path
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.trace_view import render_trace
from agentforge_dashboard.components.violation_view import render_violations
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("▶️ Ejecutar Agente")
st.caption("Lanza una ejecución con la cadena completa de gobierno.")
agents = client.list_agents()
if not agents:
st.warning("No hay agentes registrados.")
st.stop()
names = [a["name"] for a in agents]
agent_name = st.selectbox("Agente", names)
# Cargar escenarios pregrabados si existen en el disco montado.
examples_dir = Path("agents") / agent_name / "examples"
example_files = sorted(examples_dir.glob("*.txt")) if examples_dir.exists() else []
col_l, col_r = st.columns([3, 1])
with col_r:
st.markdown("**Escenarios**")
for ef in example_files:
if st.button(ef.stem, use_container_width=True):
st.session_state["input_text"] = ef.read_text(encoding="utf-8")
with col_l:
user_input = st.text_area(
"Descripción del incidente",
height=240,
key="input_text",
placeholder="Pega aquí la descripción del incidente o usa un escenario...",
)
if st.button("🚀 Invocar agente", type="primary", disabled=not user_input):
with st.spinner("Ejecutando..."):
result = client.invoke_agent(agent_name, {"input": user_input})
st.session_state["last_execution"] = result
execution = st.session_state.get("last_execution")
if execution and "error" not in execution:
st.divider()
st.markdown(f"**trace_id:** `{execution['trace_id']}`")
st.markdown(f"**Status:** `{execution['status']}`")
if execution["status"] == "awaiting_approval":
st.warning(
"Esta ejecución requiere aprobación humana. "
"Ve a la página **Aprobaciones** para revisar y decidir."
)
if execution.get("final_output"):
st.subheader("📦 Output final")
st.json(execution["final_output"])
render_violations(execution.get("violations", []))
render_trace(execution.get("decision_path", []))
elif execution and "error" in execution:
st.error(f"Error de la API: {execution['error']}")