Align runs and inbox native surfaces

This commit is contained in:
ZacharyZhang-NY committed 2026-04-19 13:53:51 -04:00
1 parent b3d5bcdde5
commit e1b7f52444
2 files changed
+634 -153

No files matched your search

@@ -2,61 +2,263 @@ 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()
.gap_4()
.flex_col()
.gap_6()
.child(
div()
.flex_1()
.p_4()
.gap_2()
.border_1()
.rounded_md()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(div().text_lg().child("Unread messages"))
.children(
self.snapshot
.agent_messages
.iter()
.filter(|item| item.read_at.is_none())
.take(8)
.map(|item| {
.flex()
.items_center()
.justify_between()
.child(
div()
.child(
div()
.py_2()
.border_b_1()
.border_color(theme::panel_inset_border())
.child(format!("{} · {}", item.subject, item.body))
}),
.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(
div()
.flex_1()
.p_4()
.gap_2()
.border_1()
.rounded_md()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(div().text_lg().child("Pending approvals"))
.children(
self.snapshot
.approvals
.iter()
.filter(|item| item.state == "pending")
.take(8)
.map(|item| {
div()
.py_2()
.border_b_1()
.border_color(theme::panel_inset_border())
.child(format!(
"{} · {}",
item.approval_type, item.payload_summary
))
.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)),
)
}
@@ -7,150 +7,429 @@ impl AppRoot {
.selected_run_id
.as_ref()
.and_then(|id| self.snapshot.runs.iter().find(|item| &item.id == id));
let selected_status = selected
.map(|item| item.status.clone())
.unwrap_or_else(|| "Select a run".to_string());
let selected_summary = selected
.map(|item| item.summary.clone())
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| "No run selected.".to_string());
let selected_model = selected
.and_then(|item| item.model.clone())
.unwrap_or_else(|| "model pending".to_string());
let selected_agent = selected
.and_then(|run| {
self.snapshot
.agents
.iter()
.find(|agent| agent.id == run.agent_id)
.map(|agent| agent.name.clone())
})
.unwrap_or_else(|| "Agent unavailable".to_string());
let selected_workspace = selected
.and_then(|run| {
run.workspace_id.as_ref().and_then(|workspace_id| {
self.snapshot
.workspaces
.iter()
.find(|workspace| &workspace.id == workspace_id)
.map(|workspace| workspace.name.clone())
})
})
.unwrap_or_else(|| "Workspace pending".to_string());
let selected_error = selected
.and_then(|item| item.error_message.clone())
.unwrap_or_default();
div()
.flex()
.gap_4()
.gap_6()
.child(
div()
.id("runs-list")
.w(px(360.0))
.p_4()
.gap_2()
.w(px(420.0))
.p_5()
.gap_4()
.border_1()
.rounded_md()
.rounded_lg()
.border_color(theme::panel_border())
.bg(theme::panel())
.overflow_scroll()
.child(div().text_lg().child("Run queue"))
.child(
div()
.flex()
.justify_between()
.items_center()
.child(
div()
.child(
div()
.text_xl()
.text_color(theme::text_primary())
.child("Run queue"),
)
.child(
div()
.mt(px(6.0))
.text_sm()
.text_color(theme::text_muted())
.child(format!("{} runs", self.snapshot.runs.len())),
),
)
.child(run_filter_shell()),
)
.child(run_search_shell("Search runs"))
.children(self.snapshot.runs.iter().map(|item| {
let active =
self.state.selected_run_id.as_deref() == Some(item.id.as_str());
let run_id = item.id.clone();
let headline = if item.summary.trim().is_empty() {
format!("{} · {}", item.status, item.id)
let title = run_task_title(&self.snapshot, item);
let subtitle = run_subtitle(&self.snapshot, item);
let summary = if item.summary.trim().is_empty() {
"No summary yet".to_string()
} else {
format!("{} · {}", item.status, item.summary)
item.summary.clone()
};
div()
.id(SharedString::from(format!("run-{}", item.id)))
.px_3()
.py_2()
.rounded_md()
.cursor_pointer()
.p_4()
.rounded_lg()
.border_1()
.border_color(if active {
theme::selection_border()
} else {
theme::panel_border()
})
.bg(if active {
theme::selection_fill()
} else {
theme::panel()
})
.cursor_pointer()
.hover(|style| style.bg(theme::panel_hover()))
.on_click(cx.listener(move |this, _, _, cx| {
this.state.selected_run_id = Some(run_id.clone());
cx.notify();
}))
.child(headline)
.child(
div()
.flex()
.justify_between()
.items_center()
.child(
div()
.flex_1()
.child(
div()
.text_base()
.text_color(theme::text_primary())
.truncate()
.child(title),
)
.child(
div()
.mt(px(6.0))
.text_sm()
.text_color(theme::text_muted())
.child(subtitle),
),
)
.child(run_status_badge(item.status.as_str())),
)
.child(
div()
.mt(px(10.0))
.text_sm()
.text_color(theme::text_secondary())
.line_clamp(2)
.child(summary),
)
.when_some(item.updated_at.get(..10), |container, date| {
container.child(
div()
.mt(px(10.0))
.text_xs()
.text_color(theme::text_subtle())
.child(date.to_string()),
)
})
})),
)
.child(
div()
.flex_1()
.p_4()
.gap_3()
.border_1()
.rounded_md()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(div().text_lg().child("Run inspector"))
.child(
div()
.text_sm()
.text_color(theme::text_muted())
.child(selected_status),
)
.child(
div()
.flex()
.gap_2()
.child(self.metric_card("Agent", selected_agent, "Assigned operator"))
.child(self.metric_card(
"Model",
selected_model,
"Runtime-selected model",
)),
)
.child(
div()
.flex()
.gap_2()
.child(self.metric_card(
"Workspace",
selected_workspace,
"Bound workspace",
))
.gap_5()
.when_some(selected, |container, run| {
container
.child(
self.metric_card(
"Connector",
selected
.map(|item| item.connector_id.clone())
.unwrap_or_else(|| "connector pending".to_string()),
"Execution provider",
),
),
)
.child(
div()
.min_h(px(180.0))
.p_3()
.border_1()
.border_color(theme::panel_inset_border())
.rounded_md()
.bg(theme::panel_inset())
.child(selected_summary),
)
.when(!selected_error.is_empty(), |container| {
div()
.p_5()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(
div()
.text_xl()
.text_color(theme::text_primary())
.child(run_task_title(&self.snapshot, run)),
)
.child(
div()
.mt(px(8.0))
.text_sm()
.text_color(theme::text_muted())
.child(run.status.clone()),
)
.child(
div()
.mt(px(16.0))
.flex()
.gap_3()
.child(info_tile(
"AGENT",
run_agent_name(&self.snapshot, run),
))
.child(info_tile(
"MODEL",
run.model.clone().unwrap_or_else(|| "Pending".to_string()),
)),
)
.child(
div()
.mt(px(12.0))
.flex()
.gap_3()
.child(info_tile(
"WORKSPACE",
run_workspace_name(&self.snapshot, run),
))
.child(info_tile(
"CONNECTOR",
run.connector_id.clone(),
)),
)
.child(
div()
.mt(px(18.0))
.p_4()
.rounded_lg()
.border_1()
.border_color(theme::panel_inset_border())
.bg(theme::panel_inset())
.child(if run.summary.trim().is_empty() {
"No run summary yet.".to_string()
} else {
run.summary.clone()
}),
)
.when_some(run.error_message.clone(), |content, error| {
if error.trim().is_empty() {
content
} else {
content.child(
div()
.mt(px(16.0))
.p_4()
.rounded_lg()
.border_1()
.border_color(theme::danger())
.bg(theme::danger_bg())
.child(error),
)
}
}),
)
.child(
div()
.p_5()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.child(
div()
.flex()
.justify_between()
.items_center()
.child(
div()
.text_lg()
.text_color(theme::text_primary())
.child("Run timeline"),
)
.child(
div()
.text_sm()
.text_color(theme::text_muted())
.child(short_date(run.updated_at.as_str()).to_string()),
),
)
.child(run_timeline(&self.snapshot, run)),
)
})
.when(selected.is_none(), |container| {
container.child(
div()
.p_3()
.min_h(px(320.0))
.flex()
.items_center()
.justify_center()
.rounded_lg()
.border_1()
.border_color(theme::primary_action())
.rounded_md()
.bg(theme::danger_bg())
.child(selected_error),
.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("No run selected"),
)
.child(
div()
.mt(px(10.0))
.text_base()
.text_color(theme::text_muted())
.child("Choose a run from the queue to inspect its status and execution context."),
),
),
)
}),
)
}
}
fn run_task_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 {}", short_id(run.id.as_str()))
} else {
run.summary.clone()
}
})
}
fn run_subtitle(snapshot: &domain::ProfileSnapshot, run: &domain::Run) -> String {
format!(
"{} via {}{}",
run_agent_name(snapshot, run),
run.connector_id,
run.workspace_id
.as_ref()
.map(|_| format!(" · {}", run_workspace_name(snapshot, run)))
.unwrap_or_default()
)
}
fn run_agent_name(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())
}
fn run_workspace_name(snapshot: &domain::ProfileSnapshot, run: &domain::Run) -> String {
run.workspace_id
.as_ref()
.and_then(|workspace_id| {
snapshot
.workspaces
.iter()
.find(|workspace| &workspace.id == workspace_id)
})
.map(|workspace| workspace.name.clone())
.unwrap_or_else(|| "Workspace pending".to_string())
}
fn run_status_badge(status: &str) -> impl IntoElement {
div()
.px_3()
.py_2()
.rounded_full()
.bg(match status {
"running" => theme::accent_teal_soft(),
"failed" | "timed_out" | "cancelled" | "interrupted" => theme::danger_bg(),
_ => theme::panel_muted(),
})
.text_xs()
.text_color(match status {
"running" => theme::accent_teal(),
"failed" | "timed_out" | "cancelled" | "interrupted" => theme::danger(),
_ => theme::text_secondary(),
})
.child(status.to_uppercase())
}
fn info_tile(label: &'static str, value: String) -> impl IntoElement {
div()
.flex_1()
.p_4()
.rounded_lg()
.bg(theme::panel_muted())
.child(div().text_xs().text_color(theme::text_muted()).child(label))
.child(
div()
.mt(px(8.0))
.text_sm()
.text_color(theme::text_primary())
.child(value),
)
}
fn run_timeline(snapshot: &domain::ProfileSnapshot, run: &domain::Run) -> impl IntoElement {
div()
.mt(px(16.0))
.gap_3()
.child(timeline_row(
"Created",
short_date(run.created_at.as_str()).to_string(),
))
.child(timeline_row(
"Started",
run.started_at
.as_deref()
.map(short_date)
.unwrap_or("Pending")
.to_string(),
))
.child(timeline_row(
"Task",
run.task_id
.as_ref()
.and_then(|task_id| snapshot.tasks.iter().find(|task| &task.id == task_id))
.map(|task| task.status.clone())
.unwrap_or_else(|| "Unlinked".to_string()),
))
.child(timeline_row(
"Updated",
short_date(run.updated_at.as_str()).to_string(),
))
}
fn timeline_row(label: &'static str, value: String) -> impl IntoElement {
div()
.flex()
.justify_between()
.items_center()
.py_2()
.border_b_1()
.border_color(theme::panel_border())
.child(div().text_sm().text_color(theme::text_muted()).child(label))
.child(
div()
.text_sm()
.text_color(theme::text_primary())
.child(value),
)
}
fn run_filter_shell() -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_secondary())
.child("All")
}
fn run_search_shell(label: &'static str) -> impl IntoElement {
div()
.px_4()
.py_3()
.rounded_lg()
.border_1()
.border_color(theme::panel_border())
.bg(theme::panel())
.text_sm()
.text_color(theme::text_subtle())
.child(label)
}
fn short_id(value: &str) -> &str {
value.get(..8).unwrap_or(value)
}
fn short_date(value: &str) -> &str {
value.get(..10).unwrap_or(value)
}