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)