Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions frontend/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""AgentLoop - Closed-loop outcome intelligence Demo Dashboard.

Run with: streamlit run frontend/app.py (from the AgentLoop repo root)
Connects to a running backend (uvicorn app.main:app in backend/) if reachable;
otherwise shows a synthetic demo dataset. Requires: pandas, plotly, streamlit, requests.
"""

from __future__ import annotations

import random
from datetime import datetime, timedelta

import pandas as pd
import plotly.express as px
import requests
import streamlit as st

st.set_page_config(
page_title="AgentLoop",
page_icon="",
layout="wide",
initial_sidebar_state="expanded",
)

DEFAULT_API = "http://localhost:8000"


def make_demo_data() -> tuple[pd.DataFrame, dict]:
random.seed(42)
paths = ["plan->tool->answer", "retrieve->rerank->generate", "agent->human", "direct"]
outcomes = ["resolved", "escalated", "converted", "retained"]
n = 120
rows = []
now = datetime.utcnow()
for i in range(n):
rows.append(
{
"session_id": f"s{i:04d}",
"agent_version": f"v{random.randint(1, 3)}",
"path": random.choice(paths),
"outcome": random.choice(outcomes),
"latency_ms": random.randint(80, 4200),
"cost_usd": round(random.uniform(0.01, 0.9), 3),
"csat": random.randint(1, 5),
"created_at": now - timedelta(days=random.randint(0, 29), hours=random.randint(0, 23)),
}
)
df = pd.DataFrame(rows)
metrics = {
"total_sessions": n,
"resolution_rate": round((df.outcome != "escalated").mean(), 3),
"avg_latency_ms": int(df.latency_ms.mean()),
"avg_csat": round(df.csat.mean(), 2),
}
return df, metrics


def fetch_live(api: str):
try:
r = requests.get(f"{api}/api/v1/analytics", timeout=5)
r.raise_for_status()
return r.json(), None
except Exception as exc: # noqa: BLE001
return None, str(exc)


def main():
with st.sidebar:
st.header("Backend")
api = st.text_input("API base URL", value=DEFAULT_API)
use_live = st.toggle("Use live backend", value=False)
refresh = st.button("Refresh", type="primary", use_container_width=True)

if use_live:
payload, err = fetch_live(api)
if err is not None:
st.warning(f"Could not reach backend: {err}\n\nShowing synthetic demo data instead.")
use_live = False

if use_live:
st.caption(f"Live data from {api}")
path_df = pd.DataFrame(payload.get("path_analysis", []))
comp_df = pd.DataFrame(payload.get("agent_comparison", []))
om = payload.get("outcome_metrics") or {}
else:
df, metrics = make_demo_data()
path_df = (
df.groupby("path")
.agg(total_sessions=("session_id", "count"), success_count=("outcome", lambda s: (s != "escalated").sum()))
.reset_index()
)
path_df["success_rate"] = (path_df.success_count / path_df.total_sessions).round(3)
path_df["avg_latency_ms"] = df.groupby("path").latency_ms.mean().values.astype(int)
comp_df = (
df.groupby("agent_version")
.agg(session_count=("session_id", "count"))
.reset_index()
)
comp_df["success_rate"] = df.groupby("agent_version").outcome.apply(lambda s: (s != "escalated").mean()).values
om = {
"total_sessions": metrics["total_sessions"],
"resolution_rate": metrics["resolution_rate"],
"avg_latency_ms": metrics["avg_latency_ms"],
"avg_csat": metrics["avg_csat"],
}
st.caption("Synthetic demo data (start the backend to see live analytics)")

st.title("AgentLoop")
st.caption("Closed-loop outcome intelligence platform")

c1, c2, c3, c4 = st.columns(4)
c1.metric("Sessions", om.get("total_sessions", 0))
c2.metric("Resolution rate", f"{om.get('resolution_rate', 0)*100:.1f}%")
c3.metric("Avg latency", f"{om.get('avg_latency_ms', 0)} ms")
c4.metric("Avg CSAT", om.get("avg_csat", "-"))

st.subheader("Path Analysis")
if not path_df.empty:
fig = px.bar(path_df, x="path", y="total_sessions", color="success_rate", color_continuous_scale="greens")
st.plotly_chart(fig, use_container_width=True)
st.dataframe(path_df)

st.subheader("Agent Version Comparison")
if not comp_df.empty:
fig2 = px.bar(comp_df, x="agent_version", y="session_count", color="success_rate", color_continuous_scale="blues")
st.plotly_chart(fig2, use_container_width=True)
st.dataframe(comp_df)

if not use_live and refresh:
pass


main()
Loading