feat(dashboard): página de Registro con detalle, versiones y diff coloreado

- components/diff_view.render_unified_diff: pinta +/-/@@ del unified diff.
- pages/1_🏛️_Registro.py: selector de agente, detalle (estado, owner, propósito,
  guardrails, system_prompt, output_schema, llm), tabla de versiones y comparador.
- pyproject: per-file-ignore N999 en pages/* — Streamlit exige nombres
  "<n>_<emoji>_<Label>.py" para la navegación multipágina.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Juan
2026-05-11 12:41:00 +02:00
co-authored by Claude Opus 4.7
parent a27e972360
commit f2ef01d98d
3 changed files with 91 additions and 0 deletions
@@ -0,0 +1,26 @@
"""Componente para renderizar diffs unificados con coloreado básico."""
from __future__ import annotations
import streamlit as st
def render_unified_diff(diff_text: str) -> None:
if not diff_text.strip():
st.info("Sin diferencias entre las versiones seleccionadas.")
return
lines: list[str] = []
for line in diff_text.splitlines():
if line.startswith("+") and not line.startswith("+++"):
lines.append(f'<span style="color:#22c55e">{line}</span>')
elif line.startswith("-") and not line.startswith("---"):
lines.append(f'<span style="color:#ef4444">{line}</span>')
elif line.startswith("@@"):
lines.append(f'<span style="color:#3b82f6;font-weight:bold">{line}</span>')
else:
lines.append(line)
html = (
"<pre style='background:#0f172a;color:#e2e8f0;padding:12px;border-radius:6px;"
"font-size:12px;overflow:auto'>" + "\n".join(lines) + "</pre>"
)
st.markdown(html, unsafe_allow_html=True)
@@ -0,0 +1,60 @@
"""Página de registro de agentes: lista, detalle, versiones y diff."""
from __future__ import annotations
import streamlit as st
from agentforge_dashboard.client import CoreClient
from agentforge_dashboard.components.diff_view import render_unified_diff
@st.cache_resource
def get_client() -> CoreClient:
return CoreClient()
client = get_client()
st.title("🏛️ Registro de Agentes")
st.caption("Catálogo central de agentes con versionado tipo Git.")
agents = client.list_agents()
if not agents:
st.warning("No hay agentes registrados. Coloca YAMLs bajo `agents/<name>/`.")
st.stop()
names = [a["name"] for a in agents]
selected_name = st.selectbox("Agente", names)
agent = client.get_agent(selected_name)
col1, col2 = st.columns([2, 1])
with col1:
st.subheader(f"{agent['name']} @ {agent['version']}")
st.write(f"**Estado:** `{agent['state']}` | **Owner:** {agent['owner']}")
st.write(f"**Propósito:** {agent['purpose']}")
st.write(f"**Guardrails activos:** {', '.join(agent['guardrails'])}")
st.write(f"**Threshold HITL:** risk_score ≥ {agent['risk_threshold_for_hitl']}")
with st.expander("System prompt"):
st.code(agent["system_prompt"], language="markdown")
with st.expander("Output schema"):
st.json(agent["output_schema"])
with col2:
st.subheader("LLM")
st.json(agent["llm"])
st.divider()
st.subheader("Historial de versiones")
versions = client.list_versions(selected_name)
st.dataframe(versions, hide_index=True, use_container_width=True)
if len(versions) >= 2:
st.subheader("Comparar versiones")
ids = [v["id"] for v in versions]
cf, ct = st.columns(2)
with cf:
v_from = st.selectbox("Desde", ids, index=0)
with ct:
v_to = st.selectbox("Hasta", ids, index=len(ids) - 1)
if v_from != v_to:
diff = client.diff_versions(selected_name, v_from, v_to)
render_unified_diff(diff["unified_diff"])
+5
View File
@@ -17,6 +17,11 @@ target-version = "py311"
select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "ASYNC", "RUF"] select = ["E", "F", "W", "I", "N", "UP", "B", "C4", "SIM", "ASYNC", "RUF"]
ignore = ["E501"] ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
# Streamlit exige que las páginas se llamen "<n>_<emoji>_<Label>.py" para la
# navegación multipágina; eso choca con N999 (nombre de módulo no válido).
"dashboard/src/agentforge_dashboard/pages/*" = ["N999"]
[tool.ruff.lint.isort] [tool.ruff.lint.isort]
known-first-party = ["agentforge_core", "agentforge_dashboard"] known-first-party = ["agentforge_core", "agentforge_dashboard"]