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
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -279,9 +279,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "agentic-coding-protocol"
|
||||
version = "0.0.9"
|
||||
version = "0.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e276b798eddd02562a339340a96919d90bbfcf78de118fdddc932524646fac7"
|
||||
checksum = "a3e6ae951b36fa2f8d9dd6e1af6da2fcaba13d7c866cf6a9e65deda9dc6c5fe4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
|
|
|
@ -410,7 +410,7 @@ zlog_settings = { path = "crates/zlog_settings" }
|
|||
# External crates
|
||||
#
|
||||
|
||||
agentic-coding-protocol = "0.0.9"
|
||||
agentic-coding-protocol = "0.0.10"
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" }
|
||||
any_vec = "0.14"
|
||||
|
|
4
assets/icons/todo_complete.svg
Normal file
4
assets/icons/todo_complete.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 13C10.7614 13 13 10.7614 13 8C13 5.23858 10.7614 3 8 3C5.23858 3 3 5.23858 3 8C3 10.7614 5.23858 13 8 13Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 8L7.33333 9L10 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 418 B |
10
assets/icons/todo_pending.svg
Normal file
10
assets/icons/todo_pending.svg
Normal file
|
@ -0,0 +1,10 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
11
assets/icons/todo_progress.svg
Normal file
11
assets/icons/todo_progress.svg
Normal file
|
@ -0,0 +1,11 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 3C7.66045 3 8.33955 3 9 3" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M11 4C11.3949 4.26602 11.7345 4.60558 12 5" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13 7C13 7.66045 13 8.33955 13 9" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 11C11.734 11.3949 11.3944 11.7345 11 12" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 13C8.33954 13 7.66046 13 7 13" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M5 12C4.6051 11.734 4.26554 11.3944 4 11" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M3 9C3 8.33955 3 7.66045 3 7" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 5C4.26602 4.6051 4.60558 4.26554 5 4" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8.00016 8.66665C8.36835 8.66665 8.66683 8.36817 8.66683 7.99998C8.66683 7.63179 8.36835 7.33331 8.00016 7.33331C7.63197 7.33331 7.3335 7.63179 7.3335 7.99998C7.3335 8.36817 7.63197 8.66665 8.00016 8.66665Z" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -453,9 +453,69 @@ impl Diff {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Plan {
|
||||
pub entries: Vec<PlanEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlanStats<'a> {
|
||||
pub in_progress_entry: Option<&'a PlanEntry>,
|
||||
pub pending: u32,
|
||||
pub completed: u32,
|
||||
}
|
||||
|
||||
impl Plan {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> PlanStats<'_> {
|
||||
let mut stats = PlanStats {
|
||||
in_progress_entry: None,
|
||||
pending: 0,
|
||||
completed: 0,
|
||||
};
|
||||
|
||||
for entry in &self.entries {
|
||||
match &entry.status {
|
||||
acp::PlanEntryStatus::Pending => {
|
||||
stats.pending += 1;
|
||||
}
|
||||
acp::PlanEntryStatus::InProgress => {
|
||||
stats.in_progress_entry = stats.in_progress_entry.or(Some(entry));
|
||||
}
|
||||
acp::PlanEntryStatus::Completed => {
|
||||
stats.completed += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PlanEntry {
|
||||
pub content: Entity<Markdown>,
|
||||
pub priority: acp::PlanEntryPriority,
|
||||
pub status: acp::PlanEntryStatus,
|
||||
}
|
||||
|
||||
impl PlanEntry {
|
||||
pub fn from_acp(entry: acp::PlanEntry, cx: &mut App) -> Self {
|
||||
Self {
|
||||
content: cx.new(|cx| Markdown::new_text(entry.content.into(), cx)),
|
||||
priority: entry.priority,
|
||||
status: entry.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AcpThread {
|
||||
entries: Vec<AgentThreadEntry>,
|
||||
title: SharedString,
|
||||
entries: Vec<AgentThreadEntry>,
|
||||
plan: Plan,
|
||||
project: Entity<Project>,
|
||||
action_log: Entity<ActionLog>,
|
||||
shared_buffers: HashMap<Entity<Buffer>, BufferSnapshot>,
|
||||
|
@ -515,6 +575,7 @@ impl AcpThread {
|
|||
action_log,
|
||||
shared_buffers: Default::default(),
|
||||
entries: Default::default(),
|
||||
plan: Default::default(),
|
||||
title,
|
||||
project,
|
||||
send_task: None,
|
||||
|
@ -819,6 +880,29 @@ impl AcpThread {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn plan(&self) -> &Plan {
|
||||
&self.plan
|
||||
}
|
||||
|
||||
pub fn update_plan(&mut self, request: acp::UpdatePlanParams, cx: &mut Context<Self>) {
|
||||
self.plan = Plan {
|
||||
entries: request
|
||||
.entries
|
||||
.into_iter()
|
||||
.map(|entry| PlanEntry::from_acp(entry, cx))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn clear_completed_plan_entries(&mut self, cx: &mut Context<Self>) {
|
||||
self.plan
|
||||
.entries
|
||||
.retain(|entry| !matches!(entry.status, acp::PlanEntryStatus::Completed));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_project_location(&self, location: ToolCallLocation, cx: &mut Context<Self>) {
|
||||
self.project.update(cx, |project, cx| {
|
||||
let Some(path) = project.project_path_for_absolute_path(&location.path, cx) else {
|
||||
|
@ -1136,6 +1220,17 @@ impl AcpClientDelegate {
|
|||
Self { thread, cx }
|
||||
}
|
||||
|
||||
pub async fn clear_completed_plan_entries(&self) -> Result<()> {
|
||||
let cx = &mut self.cx.clone();
|
||||
cx.update(|cx| {
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.clear_completed_plan_entries(cx))
|
||||
})?
|
||||
.context("Failed to update thread")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn request_existing_tool_call_confirmation(
|
||||
&self,
|
||||
tool_call_id: ToolCallId,
|
||||
|
@ -1233,6 +1328,18 @@ impl acp::Client for AcpClientDelegate {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_plan(&self, request: acp::UpdatePlanParams) -> Result<(), acp::Error> {
|
||||
let cx = &mut self.cx.clone();
|
||||
|
||||
cx.update(|cx| {
|
||||
self.thread
|
||||
.update(cx, |thread, cx| thread.update_plan(request, cx))
|
||||
})?
|
||||
.context("Failed to update thread")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn read_text_file(
|
||||
&self,
|
||||
request: acp::ReadTextFileParams,
|
||||
|
|
|
@ -153,6 +153,7 @@ impl AgentServer for ClaudeCode {
|
|||
let handler_task = cx.foreground_executor().spawn({
|
||||
let end_turn_tx = end_turn_tx.clone();
|
||||
let tool_id_map = tool_id_map.clone();
|
||||
let delegate = delegate.clone();
|
||||
async move {
|
||||
while let Some(message) = incoming_message_rx.next().await {
|
||||
ClaudeAgentConnection::handle_message(
|
||||
|
@ -167,6 +168,7 @@ impl AgentServer for ClaudeCode {
|
|||
});
|
||||
|
||||
let mut connection = ClaudeAgentConnection {
|
||||
delegate,
|
||||
outgoing_tx,
|
||||
end_turn_tx,
|
||||
_handler_task: handler_task,
|
||||
|
@ -186,6 +188,7 @@ impl AgentConnection for ClaudeAgentConnection {
|
|||
&self,
|
||||
params: AnyAgentRequest,
|
||||
) -> LocalBoxFuture<'static, Result<acp::AnyAgentResult>> {
|
||||
let delegate = self.delegate.clone();
|
||||
let end_turn_tx = self.end_turn_tx.clone();
|
||||
let outgoing_tx = self.outgoing_tx.clone();
|
||||
async move {
|
||||
|
@ -201,6 +204,8 @@ impl AgentConnection for ClaudeAgentConnection {
|
|||
Err(anyhow!("Authentication not supported"))
|
||||
}
|
||||
AnyAgentRequest::SendUserMessageParams(message) => {
|
||||
delegate.clear_completed_plan_entries().await?;
|
||||
|
||||
let (tx, rx) = oneshot::channel();
|
||||
end_turn_tx.borrow_mut().replace(tx);
|
||||
let mut content = String::new();
|
||||
|
@ -241,6 +246,7 @@ impl AgentConnection for ClaudeAgentConnection {
|
|||
}
|
||||
|
||||
struct ClaudeAgentConnection {
|
||||
delegate: AcpClientDelegate,
|
||||
outgoing_tx: UnboundedSender<SdkMessage>,
|
||||
end_turn_tx: Rc<RefCell<Option<oneshot::Sender<Result<()>>>>>,
|
||||
_mcp_server: Option<ClaudeMcpServer>,
|
||||
|
@ -267,8 +273,17 @@ impl ClaudeAgentConnection {
|
|||
.log_err();
|
||||
}
|
||||
ContentChunk::ToolUse { id, name, input } => {
|
||||
if let Some(resp) = delegate
|
||||
.push_tool_call(ClaudeTool::infer(&name, input).as_acp())
|
||||
let claude_tool = ClaudeTool::infer(&name, input);
|
||||
|
||||
if let ClaudeTool::TodoWrite(Some(params)) = claude_tool {
|
||||
delegate
|
||||
.update_plan(acp::UpdatePlanParams {
|
||||
entries: params.todos.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
.await
|
||||
.log_err();
|
||||
} else if let Some(resp) = delegate
|
||||
.push_tool_call(claude_tool.as_acp())
|
||||
.await
|
||||
.log_err()
|
||||
{
|
||||
|
|
|
@ -614,6 +614,16 @@ pub enum TodoPriority {
|
|||
Low,
|
||||
}
|
||||
|
||||
impl Into<acp::PlanEntryPriority> for TodoPriority {
|
||||
fn into(self) -> acp::PlanEntryPriority {
|
||||
match self {
|
||||
TodoPriority::High => acp::PlanEntryPriority::High,
|
||||
TodoPriority::Medium => acp::PlanEntryPriority::Medium,
|
||||
TodoPriority::Low => acp::PlanEntryPriority::Low,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TodoStatus {
|
||||
|
@ -622,6 +632,16 @@ pub enum TodoStatus {
|
|||
Completed,
|
||||
}
|
||||
|
||||
impl Into<acp::PlanEntryStatus> for TodoStatus {
|
||||
fn into(self) -> acp::PlanEntryStatus {
|
||||
match self {
|
||||
TodoStatus::Pending => acp::PlanEntryStatus::Pending,
|
||||
TodoStatus::InProgress => acp::PlanEntryStatus::InProgress,
|
||||
TodoStatus::Completed => acp::PlanEntryStatus::Completed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, JsonSchema, Debug)]
|
||||
pub struct Todo {
|
||||
/// Unique identifier
|
||||
|
@ -634,6 +654,16 @@ pub struct Todo {
|
|||
pub status: TodoStatus,
|
||||
}
|
||||
|
||||
impl Into<acp::PlanEntry> for Todo {
|
||||
fn into(self) -> acp::PlanEntry {
|
||||
acp::PlanEntry {
|
||||
content: self.content,
|
||||
priority: self.priority.into(),
|
||||
status: self.status.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema, Debug)]
|
||||
pub struct TodoWriteToolParams {
|
||||
pub todos: Vec<Todo>,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -256,6 +256,9 @@ pub enum IconName {
|
|||
TextSnippet,
|
||||
ThumbsDown,
|
||||
ThumbsUp,
|
||||
TodoComplete,
|
||||
TodoPending,
|
||||
TodoProgress,
|
||||
ToolBulb,
|
||||
ToolCopy,
|
||||
ToolDeleteFile,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue