Files
larry/dashboard/src/agentforge_dashboard/pages/3_🤝_Aprobaciones.py
T
JuanandClaude Opus 4.7 826d5b494e feat(dashboard): página Aprobaciones con HITL approve/reject por acción
Lista ejecuciones en awaiting_approval; por cada acción de needs_human_for
muestra risk_score coloreado, target y rollback_plan con un checkbox de
aprobación. Aprueba el subconjunto seleccionado (con comentario) o rechaza con
razón vía /approve y /reject; rerun tras la decisión.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 12:44:07 +02:00

83 lines
2.8 KiB
Python

"""Página de aprobaciones pendientes (HITL)."""
from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("🤝 Aprobaciones pendientes")
st.caption("Ejecuciones pausadas en HITL. Revisa cada acción propuesta y aprueba o rechaza.")
executions = client.list_executions()
pending = [e for e in executions if e["status"] == "awaiting_approval"]
if not pending:
st.success("No hay aprobaciones pendientes.")
st.stop()
selected = st.selectbox(
"Ejecución",
pending,
format_func=lambda e: f"{e['trace_id'][:8]}{e['agent_name']} @ {e['agent_version']}",
)
execution = client.get_execution(selected["trace_id"])
st.markdown(f"**trace_id:** `{execution['trace_id']}`")
st.markdown(f"**Agente:** `{execution['agent_name']}@{execution['agent_version']}`")
st.markdown(f"**Iniciada:** {execution['started_at']}")
if execution.get("final_output"):
with st.expander("Output bruto del LLM"):
st.json(execution["final_output"])
st.subheader("Acciones propuestas (requieren aprobación)")
needs = execution.get("needs_human_for") or []
if not needs:
st.info("No hay acciones que requieran aprobación.")
st.stop()
approved_ids: list[str] = []
for action in needs:
with st.container(border=True):
risk = action["risk_score"]
risk_color = {1: "🟩", 2: "🟩", 3: "🟨", 4: "🟧", 5: "🟥"}.get(risk, "⬜")
st.markdown(f"### {risk_color} `{action['action']}` → `{action['target']}`")
st.write(f"**Risk score:** {risk}/5 | Requires approval: `{action['requires_approval']}`")
st.write(f"**Rollback plan:** {action['rollback_plan']}")
if st.checkbox("Aprobar esta acción", key=f"chk_{action['id']}"):
approved_ids.append(action["id"])
comment = st.text_input("Comentario (opcional)")
col_a, col_r = st.columns(2)
with col_a:
if st.button("✅ Aprobar seleccionadas", type="primary", disabled=not approved_ids):
with st.spinner("Aplicando aprobación..."):
r = client.approve(
execution["trace_id"],
{"approved_action_ids": approved_ids, "comment": comment},
)
if "error" in r:
st.error(r["error"])
else:
st.success(f"Status: {r['status']}")
st.rerun()
with col_r:
reason = st.text_input("Razón de rechazo")
if st.button("❌ Rechazar ejecución", disabled=not reason):
with st.spinner("Aplicando rechazo..."):
r = client.reject(execution["trace_id"], {"reason": reason})
if "error" in r:
st.error(r["error"])
else:
st.success(f"Status: {r['status']}")
st.rerun()