Files
AgentCompany/AgentCompany-Rust/crates/app_ui/src/app_root/overview.rs
T
2026-04-20 08:02:40 -04:00

901 lines
31 KiB
Rust

use super::*;
impl AppRoot {
pub(super) fn render_overview(&self, cx: &mut Context<Self>) -> impl IntoElement + '_ {
let pending_approvals = self
.snapshot
.approvals
.iter()
.filter(|approval| approval.state == "pending")
.collect::<Vec<_>>();
let failed_runs = self
.snapshot
.runs
.iter()
.filter(|run| {
matches!(
run.status.as_str(),
"failed" | "timed_out" | "cancelled" | "interrupted"
)
})
.collect::<Vec<_>>();
let unread_messages = self
.snapshot
.agent_messages
.iter()
.filter(|message| message.read_at.is_none())
.collect::<Vec<_>>();
let budget_alerts = self
.snapshot
.agents
.iter()
.filter(|agent| {
agent.budget_monthly_usd > 0.0
&& agent.spent_monthly_usd >= agent.budget_monthly_usd * 0.8
})
.collect::<Vec<_>>();
let interrupted_runs = self
.snapshot
.runs
.iter()
.filter(|run| run.status == "interrupted")
.collect::<Vec<_>>();
let active_work = self
.snapshot
.runs
.iter()
.filter(|run| matches!(run.status.as_str(), "queued" | "running"))
.take(8)
.collect::<Vec<_>>();
let total_spend = self
.snapshot
.runs
.iter()
.filter_map(|run| run.cost_usd)
.sum::<f64>();
let approval_rows = pending_approvals
.iter()
.take(5)
.map(|approval| {
attention_row(
approval.payload_summary.clone(),
format!("{} · pending", approval.approval_type.replace('_', " ")),
theme::accent_orange_soft(),
theme::accent_orange(),
SectionId::Approvals,
Some(approval.id.as_str()),
cx,
)
.into_any_element()
})
.collect::<Vec<_>>();
let failed_rows = failed_runs
.iter()
.take(3)
.map(|run| {
attention_row(
overview_run_title(&self.snapshot, run),
run.error_message
.clone()
.unwrap_or_else(|| run.status.clone()),
theme::danger_bg(),
theme::danger(),
SectionId::Runs,
Some(run.id.as_str()),
cx,
)
.into_any_element()
})
.collect::<Vec<_>>();
let unread_rows = unread_messages
.iter()
.take(3)
.map(|message| {
attention_row(
message.subject.clone(),
message.body.clone(),
theme::accent_teal_soft(),
theme::accent_teal(),
SectionId::Messages,
Some(message.id.as_str()),
cx,
)
.into_any_element()
})
.collect::<Vec<_>>();
div()
.flex()
.flex_col()
.gap_6()
.when(!interrupted_runs.is_empty(), |container| {
container.child(recovery_banner(interrupted_runs.as_slice()))
})
.when(!budget_alerts.is_empty(), |container| {
container.child(budget_alert_banner(budget_alerts.as_slice(), cx))
})
.child(
div()
.grid()
.grid_cols(4)
.gap_3()
.child(metric_card(
"ACTIVE TASKS",
self.snapshot.active_tasks().to_string(),
format!(
"{} queued",
self.snapshot
.tasks
.iter()
.filter(|task| task.status == "todo")
.count()
),
theme::accent_blue_soft(),
theme::accent_blue(),
SectionId::Inbox,
cx,
))
.child(metric_card(
"APPROVALS",
pending_approvals.len().to_string(),
if pending_approvals.is_empty() {
"All clear".to_string()
} else {
"Needs attention".to_string()
},
theme::accent_orange_soft(),
theme::accent_orange(),
SectionId::Approvals,
cx,
))
.child(metric_card(
"AGENTS",
self.snapshot.agents.len().to_string(),
format!(
"{} running",
self.snapshot
.agents
.iter()
.filter(|agent| agent.status == "running")
.count()
),
theme::accent_teal_soft(),
theme::accent_teal(),
SectionId::Messages,
cx,
))
.child(metric_card(
"TOTAL SPEND",
format!("${total_spend:.2}"),
"Open settings and costs".to_string(),
theme::panel_muted(),
theme::text_primary(),
SectionId::Settings,
cx,
)),
)
.child(eyebrow_label("PERFORMANCE"))
.child(
div()
.grid()
.grid_cols(2)
.gap_3()
.child(run_activity_card(&self.snapshot))
.child(success_rate_card(&self.snapshot))
.child(cost_trend_card(&self.snapshot))
.child(agent_workload_card(&self.snapshot)),
)
.child(eyebrow_label("BUDGET UTILIZATION"))
.child(budget_utilization_card(&self.snapshot))
.when(
!(pending_approvals.is_empty()
&& failed_runs.is_empty()
&& unread_messages.is_empty()),
|container| {
container.child(needs_attention_label()).child(
div()
.gap_2()
.children(approval_rows)
.children(failed_rows)
.children(unread_rows),
)
},
)
.child(active_work_header(cx))
.child(active_work_card(&self.snapshot, active_work.as_slice(), cx))
.child(eyebrow_label("RECENT ACTIVITY"))
.child(activity_feed_card(&self.snapshot))
}
}
fn recovery_banner(runs: &[&domain::Run]) -> impl IntoElement {
let danger_30 = gpui::Hsla { a: 0.3, ..theme::danger() };
div()
.rounded(px(0.0))
.border_1()
.border_color(danger_30)
.bg(theme::warning_bg())
.px_4()
.py_4()
.mb_4()
.child(
div()
.flex()
.gap_2()
.items_center()
.child(
div()
.w(px(16.0))
.h(px(16.0))
.rounded(px(4.0))
.bg(theme::danger())
.flex()
.items_center()
.justify_center()
.child(div().w(px(10.0)).h(px(10.0)).rounded_full().bg(theme::panel())),
)
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(format!(
"{} interrupted task(s) detected from the previous session",
runs.len()
)),
),
)
.children(runs.iter().map(|run| {
div()
.mt(px(8.0))
.rounded(px(4.0))
.bg(theme::panel())
.px_4()
.py_3()
.child(
div()
.text_size(px(13.0))
.text_color(theme::text_primary())
.child(overview_run_title_from_run(run)),
)
.child(
div()
.mt(px(4.0))
.text_size(px(11.0))
.text_color(theme::text_muted())
.child(run.id.get(..8).unwrap_or(run.id.as_str()).to_string()),
)
}))
}
fn budget_alert_banner(agents: &[&domain::Agent], cx: &mut Context<AppRoot>) -> impl IntoElement {
let danger_30 = gpui::Hsla { a: 0.3, ..theme::danger() };
div().gap_2().children(agents.iter().map(|agent| {
div()
.id(SharedString::from(format!("budget-alert-{}", agent.id)))
.rounded(px(0.0))
.border_1()
.border_color(danger_30)
.bg(theme::danger_bg())
.px_4()
.py_3()
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(|this, _, _, cx| {
this.state.section = SectionId::Settings;
cx.notify();
}))
.child(
div()
.flex()
.gap_2()
.items_center()
.child(
div()
.w(px(16.0))
.h(px(16.0))
.rounded(px(4.0))
.bg(theme::danger())
.flex()
.items_center()
.justify_center()
.child(div().w(px(10.0)).h(px(10.0)).rounded_full().bg(theme::panel())),
)
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(format!(
"{} is at ${:.2} / ${:.2} monthly budget.",
agent.name, agent.spent_monthly_usd, agent.budget_monthly_usd
)),
),
)
}))
}
fn metric_card(
label: &'static str,
value: String,
subtitle: String,
icon_bg: gpui::Hsla,
_tone: gpui::Hsla,
target_section: SectionId,
cx: &mut Context<AppRoot>,
) -> impl IntoElement {
let icon_letter = match label {
"ACTIVE TASKS" => "T",
"APPROVALS" => "A",
"RUNS" => "R",
"AGENTS" => "A",
_ => "•",
};
div()
.id(SharedString::from(format!("overview-metric-{label}")))
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.px_4()
.py_4()
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.section = target_section;
cx.notify();
}))
.child(
div()
.flex()
.gap_4()
.items_center()
.child(
div()
.w(px(48.0))
.h(px(48.0))
.rounded(px(4.0))
.bg(icon_bg)
.flex()
.items_center()
.justify_center()
.child(
div()
.text_size(px(18.0))
.font_weight(gpui::FontWeight::BOLD)
.text_color(theme::text_muted())
.child(icon_letter),
),
)
.child(
div()
.flex_1()
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::text_muted())
.child(label),
)
.child(
div()
.text_size(px(28.0))
.font_weight(gpui::FontWeight::BOLD)
.text_color(theme::text_primary())
.child(value),
)
.child(
div()
.mt(px(6.0))
.text_size(px(11.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_muted())
.child(subtitle),
),
),
)
}
fn eyebrow_label(label: &'static str) -> impl IntoElement {
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::text_muted())
.child(label)
}
fn needs_attention_label() -> impl IntoElement {
div()
.flex()
.items_center()
.gap_2()
.child(
div()
.w(px(6.0))
.h(px(6.0))
.rounded_full()
.bg(theme::accent_orange()),
)
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::accent_orange())
.child("NEEDS ATTENTION"),
)
}
fn attention_row(
title: String,
subtitle: String,
tile_bg: gpui::Hsla,
accent: gpui::Hsla,
target_section: SectionId,
target_id: Option<&str>,
cx: &mut Context<AppRoot>,
) -> impl IntoElement {
let selected_id = target_id.map(str::to_string);
div()
.id(SharedString::from(format!("overview-attention-{title}")))
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.px_4()
.py_3()
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.section = target_section;
match target_section {
SectionId::Runs => this.state.selected_run_id = selected_id.clone(),
SectionId::Approvals => this.state.selected_approval_id = selected_id.clone(),
SectionId::Messages => this.state.selected_message_id = selected_id.clone(),
_ => {}
}
cx.notify();
}))
.child(
div()
.flex()
.gap_3()
.items_center()
.child(
div()
.w(px(36.0))
.h(px(36.0))
.rounded(px(4.0))
.bg(tile_bg)
.flex()
.items_center()
.justify_center()
.child(
div()
.w(px(8.0))
.h(px(8.0))
.rounded_full()
.bg(accent),
),
)
.child(
div()
.flex_1()
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(title),
)
.child(
div()
.mt(px(4.0))
.text_size(px(11.0))
.text_color(theme::text_muted())
.line_clamp(2)
.child(subtitle),
),
)
.child(
div()
.text_size(px(14.0))
.text_color(theme::text_muted())
.child(">"),
),
)
}
fn run_activity_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
let queued = snapshot
.runs
.iter()
.filter(|run| run.status == "queued")
.count();
let running = snapshot
.runs
.iter()
.filter(|run| run.status == "running")
.count();
let completed = snapshot
.runs
.iter()
.filter(|run| run.status == "completed")
.count();
mini_chart_card(
"RUN ACTIVITY",
vec![
("queued".to_string(), queued),
("running".to_string(), running),
("completed".to_string(), completed),
],
theme::accent_blue(),
)
}
fn success_rate_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
let finished = snapshot
.runs
.iter()
.filter(|run| matches!(run.status.as_str(), "completed" | "failed" | "timed_out"))
.count();
let completed = snapshot
.runs
.iter()
.filter(|run| run.status == "completed")
.count();
let rate = if finished == 0 {
0.0
} else {
completed as f32 / finished as f32
};
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.p_5()
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::text_muted())
.child("SUCCESS RATE"),
)
.child(
div()
.mt(px(10.0))
.text_size(px(28.0))
.font_weight(gpui::FontWeight::BOLD)
.text_color(theme::accent_teal())
.child(format!("{:.0}%", rate * 100.0)),
)
.child(
div()
.mt(px(10.0))
.h(px(10.0))
.rounded_full()
.bg(theme::panel_muted())
.child(
div()
.w(gpui::relative(rate.clamp(0.0, 1.0)))
.h_full()
.rounded_full()
.bg(theme::accent_teal()),
),
)
}
fn cost_trend_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
let costs = snapshot
.runs
.iter()
.filter_map(|run| run.cost_usd)
.collect::<Vec<_>>();
let total = costs.iter().sum::<f64>();
let average = if costs.is_empty() {
0.0
} else {
total / costs.len() as f64
};
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.p_5()
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::text_muted())
.child("COST TREND"),
)
.child(
div()
.mt(px(10.0))
.text_size(px(28.0))
.font_weight(gpui::FontWeight::BOLD)
.text_color(theme::text_primary())
.child(format!("${total:.2}")),
)
.child(
div()
.mt(px(8.0))
.text_size(px(11.0))
.text_color(theme::text_muted())
.child(format!("Average ${average:.2} per metered run")),
)
}
fn agent_workload_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
let rows = snapshot
.agents
.iter()
.map(|agent| {
let active_runs = snapshot
.runs
.iter()
.filter(|run| run.agent_id == agent.id)
.count();
(agent.name.clone(), active_runs)
})
.collect::<Vec<_>>();
mini_chart_card("AGENT WORKLOAD", rows, theme::accent_orange())
}
fn mini_chart_card(
label: &'static str,
rows: Vec<(String, usize)>,
accent: gpui::Hsla,
) -> impl IntoElement {
let max = rows
.iter()
.map(|(_, count)| *count)
.max()
.unwrap_or(1)
.max(1) as f32;
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.p_5()
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::SEMIBOLD)
.text_color(theme::text_muted())
.child(label),
)
.children(rows.into_iter().map(|(name, count)| {
div()
.mt(px(12.0))
.child(
div()
.flex()
.justify_between()
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(name),
)
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_muted())
.child(count.to_string()),
),
)
.child(
div()
.mt(px(6.0))
.h(px(8.0))
.rounded_full()
.bg(theme::panel_muted())
.child(
div()
.w(gpui::relative((count as f32 / max).clamp(0.0, 1.0)))
.h_full()
.rounded_full()
.bg(accent),
),
)
}))
}
fn budget_utilization_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.p_5()
.children(
snapshot
.agents
.iter()
.filter(|agent| agent.budget_monthly_usd > 0.0)
.map(|agent| {
let ratio =
(agent.spent_monthly_usd / agent.budget_monthly_usd).clamp(0.0, 1.0) as f32;
div()
.mt(px(12.0))
.child(
div()
.flex()
.justify_between()
.items_center()
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(agent.name.clone()),
)
.child(
div()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_muted())
.child(format!(
"${:.2} / ${:.2}",
agent.spent_monthly_usd, agent.budget_monthly_usd
)),
),
)
.child(
div()
.mt(px(6.0))
.h(px(8.0))
.rounded_full()
.bg(theme::panel_muted())
.child(div().w(gpui::relative(ratio)).h_full().rounded_full().bg(
if ratio >= 1.0 {
theme::danger()
} else if ratio >= 0.8 {
theme::accent_orange()
} else {
theme::accent_teal()
},
)),
)
}),
)
}
fn active_work_header(cx: &mut Context<AppRoot>) -> impl IntoElement {
div()
.flex()
.justify_between()
.items_center()
.child(eyebrow_label("ACTIVE WORK"))
.child(
div()
.id("overview-all-runs")
.cursor_pointer()
.text_size(px(11.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::accent_blue())
.hover(|style| style.text_color(theme::text_primary()))
.on_click(cx.listener(|this, _, _, cx| {
this.state.section = SectionId::Runs;
cx.notify();
}))
.child("All runs"),
)
}
fn active_work_card<'a>(
snapshot: &'a domain::ProfileSnapshot,
runs: &[&'a domain::Run],
cx: &mut Context<AppRoot>,
) -> impl IntoElement + 'a {
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.when(runs.is_empty(), |container| {
container.child(
div()
.p_6()
.text_center()
.text_color(theme::text_muted())
.child("No tasks are running right now."),
)
})
.when(!runs.is_empty(), |container| {
container.children(runs.iter().map(|run| {
let run_id = run.id.clone();
div()
.id(SharedString::from(format!("overview-run-{run_id}")))
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.section = SectionId::Runs;
this.state.selected_run_id = Some(run_id.clone());
cx.notify();
}))
.px_4()
.py_3()
.border_b_1()
.border_color(theme::panel_border())
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(overview_run_title(snapshot, run)),
)
.child(
div()
.mt(px(4.0))
.text_size(px(11.0))
.text_color(theme::text_muted())
.child(format!(
"{} · {}",
overview_run_agent(snapshot, run),
run.status
)),
)
}))
})
}
fn activity_feed_card(snapshot: &domain::ProfileSnapshot) -> impl IntoElement {
div()
.rounded(px(0.0))
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.children(snapshot.activity.iter().take(10).map(|item| {
div()
.px_4()
.py_3()
.border_b_1()
.border_color(theme::panel_border())
.child(
div()
.text_size(px(13.0))
.font_weight(gpui::FontWeight::MEDIUM)
.text_color(theme::text_primary())
.child(format!("{} {}", item.actor, item.action.replace('_', " "))),
)
.child(
div()
.mt(px(4.0))
.text_size(px(11.0))
.text_color(theme::text_muted())
.child(item.detail.clone()),
)
}))
}
fn overview_run_title(snapshot: &domain::ProfileSnapshot, run: &domain::Run) -> String {
run.task_id
.as_ref()
.and_then(|task_id| snapshot.tasks.iter().find(|task| &task.id == task_id))
.map(|task| task.title.clone())
.unwrap_or_else(|| overview_run_title_from_run(run))
}
fn overview_run_title_from_run(run: &domain::Run) -> String {
if run.summary.trim().is_empty() {
format!("Run {}", run.id.get(..8).unwrap_or(run.id.as_str()))
} else {
run.summary.clone()
}
}
fn overview_run_agent(snapshot: &domain::ProfileSnapshot, run: &domain::Run) -> String {
snapshot
.agents
.iter()
.find(|agent| agent.id == run.agent_id)
.map(|agent| agent.name.clone())
.unwrap_or_else(|| run.agent_id.clone())
}