Display ACP plans (#34816)
Release Notes: - N/A --------- Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
This commit is contained in:
parent
35b4a918c9
commit
405244d422
10 changed files with 379 additions and 34 deletions
|
@ -1,3 +1,4 @@
|
|||
use acp_thread::Plan;
|
||||
use agent_servers::AgentServer;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
|
@ -66,7 +67,8 @@ pub struct AcpThreadView {
|
|||
expanded_tool_calls: HashSet<ToolCallId>,
|
||||
expanded_thinking_blocks: HashSet<(usize, usize)>,
|
||||
edits_expanded: bool,
|
||||
editor_is_expanded: bool,
|
||||
plan_expanded: bool,
|
||||
editor_expanded: bool,
|
||||
message_history: Rc<RefCell<MessageHistory<acp::SendUserMessageParams>>>,
|
||||
}
|
||||
|
||||
|
@ -186,7 +188,8 @@ impl AcpThreadView {
|
|||
expanded_tool_calls: HashSet::default(),
|
||||
expanded_thinking_blocks: HashSet::default(),
|
||||
edits_expanded: false,
|
||||
editor_is_expanded: false,
|
||||
plan_expanded: false,
|
||||
editor_expanded: false,
|
||||
message_history,
|
||||
}
|
||||
}
|
||||
|
@ -332,14 +335,14 @@ impl AcpThreadView {
|
|||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.set_editor_is_expanded(!self.editor_is_expanded, cx);
|
||||
self.set_editor_is_expanded(!self.editor_expanded, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context<Self>) {
|
||||
self.editor_is_expanded = is_expanded;
|
||||
self.editor_expanded = is_expanded;
|
||||
self.message_editor.update(cx, |editor, _| {
|
||||
if self.editor_is_expanded {
|
||||
if self.editor_expanded {
|
||||
editor.set_mode(EditorMode::Full {
|
||||
scale_ui_elements_with_buffer_font_size: false,
|
||||
show_active_line_background: false,
|
||||
|
@ -1477,7 +1480,7 @@ impl AcpThreadView {
|
|||
container.into_any()
|
||||
}
|
||||
|
||||
fn render_edits_bar(
|
||||
fn render_activity_bar(
|
||||
&self,
|
||||
thread_entity: &Entity<AcpThread>,
|
||||
window: &mut Window,
|
||||
|
@ -1486,8 +1489,9 @@ impl AcpThreadView {
|
|||
let thread = thread_entity.read(cx);
|
||||
let action_log = thread.action_log();
|
||||
let changed_buffers = action_log.read(cx).changed_buffers(cx);
|
||||
let plan = thread.plan();
|
||||
|
||||
if changed_buffers.is_empty() {
|
||||
if changed_buffers.is_empty() && plan.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -1496,7 +1500,6 @@ impl AcpThreadView {
|
|||
let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
|
||||
|
||||
let pending_edits = thread.has_pending_edit_tool_calls();
|
||||
let expanded = self.edits_expanded;
|
||||
|
||||
v_flex()
|
||||
.mt_1()
|
||||
|
@ -1512,27 +1515,165 @@ impl AcpThreadView {
|
|||
blur_radius: px(3.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.child(self.render_edits_bar_summary(
|
||||
action_log,
|
||||
&changed_buffers,
|
||||
expanded,
|
||||
pending_edits,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(expanded, |parent| {
|
||||
parent.child(self.render_edits_bar_files(
|
||||
action_log,
|
||||
&changed_buffers,
|
||||
pending_edits,
|
||||
cx,
|
||||
))
|
||||
.when(!plan.is_empty(), |this| {
|
||||
this.child(self.render_plan_summary(plan, window, cx))
|
||||
.when(self.plan_expanded, |parent| {
|
||||
parent.child(self.render_plan_entries(plan, window, cx))
|
||||
})
|
||||
})
|
||||
.when(!changed_buffers.is_empty(), |this| {
|
||||
this.child(Divider::horizontal())
|
||||
.child(self.render_edits_summary(
|
||||
action_log,
|
||||
&changed_buffers,
|
||||
self.edits_expanded,
|
||||
pending_edits,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
.when(self.edits_expanded, |parent| {
|
||||
parent.child(self.render_edited_files(
|
||||
action_log,
|
||||
&changed_buffers,
|
||||
pending_edits,
|
||||
cx,
|
||||
))
|
||||
})
|
||||
})
|
||||
.into_any()
|
||||
.into()
|
||||
}
|
||||
|
||||
fn render_edits_bar_summary(
|
||||
fn render_plan_summary(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
|
||||
let stats = plan.stats();
|
||||
|
||||
let title = if let Some(entry) = stats.in_progress_entry
|
||||
&& !self.plan_expanded
|
||||
{
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Label::new("Current:")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(MarkdownElement::new(
|
||||
entry.content.clone(),
|
||||
plan_label_markdown_style(&entry.status, window, cx),
|
||||
)),
|
||||
)
|
||||
.when(stats.pending > 0, |this| {
|
||||
this.child(
|
||||
Label::new(format!("{} left", stats.pending))
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mr_1(),
|
||||
)
|
||||
})
|
||||
} else {
|
||||
let status_label = if stats.pending == 0 {
|
||||
"All Done".to_string()
|
||||
} else if stats.completed == 0 {
|
||||
format!("{}", plan.entries.len())
|
||||
} else {
|
||||
format!("{}/{}", stats.completed, plan.entries.len())
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.justify_between()
|
||||
.child(
|
||||
Label::new("Plan")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Label::new(status_label)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.mr_1(),
|
||||
)
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.p_1()
|
||||
.justify_between()
|
||||
.when(self.plan_expanded, |this| {
|
||||
this.border_b_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id("plan_summary")
|
||||
.w_full()
|
||||
.gap_1()
|
||||
.child(Disclosure::new("plan_disclosure", self.plan_expanded))
|
||||
.child(title)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.plan_expanded = !this.plan_expanded;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_plan_entries(&self, plan: &Plan, window: &mut Window, cx: &Context<Self>) -> Div {
|
||||
v_flex().children(plan.entries.iter().enumerate().flat_map(|(index, entry)| {
|
||||
let element = h_flex()
|
||||
.py_1()
|
||||
.px_2()
|
||||
.gap_2()
|
||||
.justify_between()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.when(index < plan.entries.len() - 1, |parent| {
|
||||
parent.border_color(cx.theme().colors().border).border_b_1()
|
||||
})
|
||||
.child(
|
||||
h_flex()
|
||||
.id(("plan_entry", index))
|
||||
.gap_1p5()
|
||||
.max_w_full()
|
||||
.overflow_x_scroll()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.child(match entry.status {
|
||||
acp::PlanEntryStatus::Pending => Icon::new(IconName::TodoPending)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::InProgress => Icon::new(IconName::TodoProgress)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Accent)
|
||||
.with_animation(
|
||||
"running",
|
||||
Animation::new(Duration::from_secs(2)).repeat(),
|
||||
|icon, delta| {
|
||||
icon.transform(Transformation::rotate(percentage(delta)))
|
||||
},
|
||||
)
|
||||
.into_any_element(),
|
||||
acp::PlanEntryStatus::Completed => Icon::new(IconName::TodoComplete)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Success)
|
||||
.into_any_element(),
|
||||
})
|
||||
.child(MarkdownElement::new(
|
||||
entry.content.clone(),
|
||||
plan_label_markdown_style(&entry.status, window, cx),
|
||||
)),
|
||||
);
|
||||
|
||||
Some(element)
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_edits_summary(
|
||||
&self,
|
||||
action_log: &Entity<ActionLog>,
|
||||
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
|
||||
|
@ -1678,7 +1819,7 @@ impl AcpThreadView {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_edits_bar_files(
|
||||
fn render_edited_files(
|
||||
&self,
|
||||
action_log: &Entity<ActionLog>,
|
||||
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
|
||||
|
@ -1831,7 +1972,7 @@ impl AcpThreadView {
|
|||
fn render_message_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
||||
let focus_handle = self.message_editor.focus_handle(cx);
|
||||
let editor_bg_color = cx.theme().colors().editor_background;
|
||||
let (expand_icon, expand_tooltip) = if self.editor_is_expanded {
|
||||
let (expand_icon, expand_tooltip) = if self.editor_expanded {
|
||||
(IconName::Minimize, "Minimize Message Editor")
|
||||
} else {
|
||||
(IconName::Maximize, "Expand Message Editor")
|
||||
|
@ -1844,7 +1985,7 @@ impl AcpThreadView {
|
|||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.bg(editor_bg_color)
|
||||
.when(self.editor_is_expanded, |this| {
|
||||
.when(self.editor_expanded, |this| {
|
||||
this.h(vh(0.8, window)).size_full().justify_between()
|
||||
})
|
||||
.child(
|
||||
|
@ -2243,7 +2384,7 @@ impl Render for AcpThreadView {
|
|||
.child(LoadingLabel::new("").size(LabelSize::Small))
|
||||
.into(),
|
||||
})
|
||||
.children(self.render_edits_bar(&thread, window, cx))
|
||||
.children(self.render_activity_bar(&thread, window, cx))
|
||||
} else {
|
||||
this.child(self.render_empty_state(cx))
|
||||
}
|
||||
|
@ -2409,3 +2550,27 @@ fn default_markdown_style(buffer_font: bool, window: &Window, cx: &App) -> Markd
|
|||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_label_markdown_style(
|
||||
status: &acp::PlanEntryStatus,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> MarkdownStyle {
|
||||
let default_md_style = default_markdown_style(false, window, cx);
|
||||
|
||||
MarkdownStyle {
|
||||
base_text_style: TextStyle {
|
||||
color: cx.theme().colors().text_muted,
|
||||
strikethrough: if matches!(status, acp::PlanEntryStatus::Completed) {
|
||||
Some(gpui::StrikethroughStyle {
|
||||
thickness: px(1.),
|
||||
color: Some(cx.theme().colors().text_muted.opacity(0.8)),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
},
|
||||
..default_md_style.base_text_style
|
||||
},
|
||||
..default_md_style
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue