265 lines
9.4 KiB
Rust
265 lines
9.4 KiB
Rust
use super::*;
|
|
|
|
impl AppRoot {
|
|
pub(super) fn render_inbox(&self) -> impl IntoElement + '_ {
|
|
let pending_approvals = self
|
|
.snapshot
|
|
.approvals
|
|
.iter()
|
|
.filter(|item| item.state == "pending")
|
|
.collect::<Vec<_>>();
|
|
let unread_messages = self
|
|
.snapshot
|
|
.agent_messages
|
|
.iter()
|
|
.filter(|item| item.read_at.is_none())
|
|
.collect::<Vec<_>>();
|
|
let review_tasks = self
|
|
.snapshot
|
|
.tasks
|
|
.iter()
|
|
.filter(|task| task.status == "in_review")
|
|
.collect::<Vec<_>>();
|
|
let failed_runs = self
|
|
.snapshot
|
|
.runs
|
|
.iter()
|
|
.filter(|run| {
|
|
matches!(
|
|
run.status.as_str(),
|
|
"failed" | "timed_out" | "cancelled" | "interrupted"
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let total_attention = pending_approvals.len()
|
|
+ unread_messages.len()
|
|
+ review_tasks.len()
|
|
+ failed_runs.len();
|
|
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_6()
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.justify_between()
|
|
.child(
|
|
div()
|
|
.child(
|
|
div()
|
|
.text_xl()
|
|
.text_color(theme::text_primary())
|
|
.child("Inbox"),
|
|
)
|
|
.child(
|
|
div()
|
|
.mt(px(6.0))
|
|
.text_sm()
|
|
.text_color(theme::text_muted())
|
|
.child(format!("{} items need attention", total_attention)),
|
|
),
|
|
),
|
|
)
|
|
.child(inbox_filters(
|
|
pending_approvals.len(),
|
|
review_tasks.len(),
|
|
failed_runs.len(),
|
|
unread_messages.len(),
|
|
))
|
|
.when(total_attention == 0, |container| {
|
|
container.child(
|
|
div()
|
|
.min_h(px(260.0))
|
|
.flex()
|
|
.items_center()
|
|
.justify_center()
|
|
.rounded_lg()
|
|
.border_1()
|
|
.border_color(theme::panel_border())
|
|
.bg(theme::panel())
|
|
.child(
|
|
div()
|
|
.w(px(420.0))
|
|
.text_center()
|
|
.child(
|
|
div()
|
|
.text_xl()
|
|
.text_color(theme::text_primary())
|
|
.child("All clear"),
|
|
)
|
|
.child(
|
|
div()
|
|
.mt(px(10.0))
|
|
.text_base()
|
|
.text_color(theme::text_muted())
|
|
.child("No approvals, reviews, failed runs, or unread internal messages need board attention right now."),
|
|
),
|
|
),
|
|
)
|
|
})
|
|
.when(total_attention > 0, |container| {
|
|
container
|
|
.when(!pending_approvals.is_empty(), |content| {
|
|
content.child(inbox_group(
|
|
"Pending approvals",
|
|
pending_approvals.into_iter().map(|item| {
|
|
inbox_row(
|
|
item.payload_summary.clone(),
|
|
format!("{} · pending", item.approval_type.replace('_', " ")),
|
|
theme::accent_orange_soft(),
|
|
theme::accent_orange(),
|
|
)
|
|
}),
|
|
))
|
|
})
|
|
.when(!review_tasks.is_empty(), |content| {
|
|
content.child(inbox_group(
|
|
"Reviews in progress",
|
|
review_tasks.into_iter().map(|item| {
|
|
inbox_row(
|
|
item.title.clone(),
|
|
format!("{} · awaiting review", item.status),
|
|
theme::accent_blue_soft(),
|
|
theme::accent_blue(),
|
|
)
|
|
}),
|
|
))
|
|
})
|
|
.when(!failed_runs.is_empty(), |content| {
|
|
content.child(inbox_group(
|
|
"Failed runs",
|
|
failed_runs.into_iter().map(|item| {
|
|
inbox_row(
|
|
inbox_run_title(&self.snapshot, item),
|
|
item.error_message.clone().unwrap_or_else(|| item.status.clone()),
|
|
theme::danger_bg(),
|
|
theme::danger(),
|
|
)
|
|
}),
|
|
))
|
|
})
|
|
.when(!unread_messages.is_empty(), |content| {
|
|
content.child(inbox_group(
|
|
"Unread messages",
|
|
unread_messages.into_iter().map(|item| {
|
|
inbox_row(
|
|
item.subject.clone(),
|
|
item.body.clone(),
|
|
theme::accent_teal_soft(),
|
|
theme::accent_teal(),
|
|
)
|
|
}),
|
|
))
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
fn inbox_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(|| {
|
|
if run.summary.trim().is_empty() {
|
|
format!("Run {}", run.id.get(..8).unwrap_or(run.id.as_str()))
|
|
} else {
|
|
run.summary.clone()
|
|
}
|
|
})
|
|
}
|
|
|
|
fn inbox_filters(
|
|
approvals: usize,
|
|
reviews: usize,
|
|
failed_runs: usize,
|
|
messages: usize,
|
|
) -> impl IntoElement {
|
|
div()
|
|
.flex()
|
|
.gap_2()
|
|
.child(filter_chip(
|
|
"All",
|
|
approvals + reviews + failed_runs + messages,
|
|
true,
|
|
))
|
|
.child(filter_chip("Approvals", approvals, false))
|
|
.child(filter_chip("Reviews", reviews, false))
|
|
.child(filter_chip("Failed", failed_runs, false))
|
|
.child(filter_chip("Messages", messages, false))
|
|
}
|
|
|
|
fn filter_chip(label: &'static str, count: usize, active: bool) -> impl IntoElement {
|
|
div()
|
|
.px_4()
|
|
.py_2()
|
|
.rounded_full()
|
|
.bg(if active {
|
|
theme::primary_action()
|
|
} else {
|
|
theme::panel_muted()
|
|
})
|
|
.text_xs()
|
|
.text_color(if active {
|
|
theme::primary_action_text()
|
|
} else {
|
|
theme::text_secondary()
|
|
})
|
|
.child(format!("{} {}", label, count))
|
|
}
|
|
|
|
fn inbox_group(label: &'static str, rows: impl Iterator<Item = gpui::Div>) -> impl IntoElement {
|
|
div()
|
|
.gap_3()
|
|
.child(div().text_xs().text_color(theme::text_muted()).child(label))
|
|
.child(
|
|
div()
|
|
.border_1()
|
|
.rounded_lg()
|
|
.border_color(theme::panel_border())
|
|
.bg(theme::panel())
|
|
.children(rows),
|
|
)
|
|
}
|
|
|
|
fn inbox_row(
|
|
title: String,
|
|
subtitle: String,
|
|
tile_bg: gpui::Hsla,
|
|
accent: gpui::Hsla,
|
|
) -> gpui::Div {
|
|
div()
|
|
.px_5()
|
|
.py_4()
|
|
.border_b_1()
|
|
.border_color(theme::panel_border())
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_3()
|
|
.items_start()
|
|
.child(div().w(px(14.0)).h(px(14.0)).rounded_full().bg(accent))
|
|
.child(
|
|
div()
|
|
.flex_1()
|
|
.child(
|
|
div()
|
|
.text_base()
|
|
.text_color(theme::text_primary())
|
|
.child(title),
|
|
)
|
|
.child(
|
|
div()
|
|
.mt(px(6.0))
|
|
.text_sm()
|
|
.text_color(theme::text_muted())
|
|
.line_clamp(2)
|
|
.child(subtitle),
|
|
),
|
|
)
|
|
.child(div().w(px(20.0)).h(px(20.0)).rounded_full().bg(tile_bg)),
|
|
)
|
|
}
|