From a36a8afa09fc75cd981f2fa64f36107ce7e52d8d Mon Sep 17 00:00:00 2001 From: Juan Date: Mon, 11 May 2026 12:53:17 +0200 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20p=C3=A1gina=20Historial=20co?= =?UTF-8?q?n=20tabs=20ejecuciones=20y=20violaciones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tab "Ejecuciones": tabla resumen + detalle (status, error, output, violations, traza) por trace_id. Tab "Violaciones": log filtrable por severidad. Co-Authored-By: Claude Opus 4.7 --- .../pages/4_πŸ“œ_Historial.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 dashboard/src/agentforge_dashboard/pages/4_πŸ“œ_Historial.py diff --git a/dashboard/src/agentforge_dashboard/pages/4_πŸ“œ_Historial.py b/dashboard/src/agentforge_dashboard/pages/4_πŸ“œ_Historial.py new file mode 100644 index 0000000..113e219 --- /dev/null +++ b/dashboard/src/agentforge_dashboard/pages/4_πŸ“œ_Historial.py @@ -0,0 +1,53 @@ +"""PΓ‘gina de historial de ejecuciones y log de violaciones.""" + +from __future__ import annotations + +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("πŸ“œ Historial") +st.caption("Ejecuciones registradas y log auditable de violaciones de guardrails.") + +tabs = st.tabs(["Ejecuciones", "Violaciones"]) + +with tabs[0]: + executions = client.list_executions() + if not executions: + st.info("AΓΊn no hay ejecuciones registradas.") + else: + st.dataframe(executions, hide_index=True, use_container_width=True) + trace_ids = [e["trace_id"] for e in executions] + selected = st.selectbox( + "Ver detalle", + trace_ids, + format_func=lambda t: t[:8] + "…", + ) + if selected: + detail = client.get_execution(selected) + st.markdown(f"**Status:** `{detail['status']}`") + if detail.get("error"): + st.error(f"Error: {detail['error']}") + if detail.get("final_output"): + st.subheader("Output final") + st.json(detail["final_output"]) + render_violations(detail.get("violations", [])) + render_trace(detail.get("decision_path", [])) + +with tabs[1]: + sev = st.selectbox("Filtrar severidad", ["(todas)", "info", "warning", "block"]) + filter_kwargs = {} if sev == "(todas)" else {"severity": sev} + violations = client.list_violations(**filter_kwargs) + if not violations: + st.success("Sin violaciones registradas con ese filtro.") + else: + st.dataframe(violations, hide_index=True, use_container_width=True)