assistant: Refine workflow step labels (#16161)

Piotr Osiewicz and Danilo created

https://github.com/user-attachments/assets/f6325507-091a-482e-ac28-dd09877ebaa2


Release Notes:

- N/A

---------

Co-authored-by: Danilo <daniloleal09@gmail.com>

Change summary

assets/icons/text-search.svg              |   1 
crates/assistant/src/assistant_panel.rs   | 145 +++++++++++++++---------
crates/assistant/src/context.rs           |  17 +-
crates/assistant/src/context_inspector.rs |   9 -
crates/ui/src/components/icon.rs          |   2 
5 files changed, 101 insertions(+), 73 deletions(-)

Detailed changes

assets/icons/text-search.svg 🔗

@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-text-search"><path d="M21 6H3"/><path d="M10 12H3"/><path d="M10 18H3"/><circle cx="17" cy="15" r="3"/><path d="m21 19-1.9-1.9"/></svg>

crates/assistant/src/assistant_panel.rs 🔗

@@ -365,7 +365,8 @@ impl AssistantPanel {
             pane.set_should_display_tab_bar(|_| true);
             pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
                 let focus_handle = pane.focus_handle(cx);
-                let left_children = IconButton::new("history", IconName::TextSearch)
+                let left_children = IconButton::new("history", IconName::HistoryRerun)
+                    .icon_size(IconSize::Small)
                     .on_click(cx.listener({
                         let focus_handle = focus_handle.clone();
                         move |_, _, cx| {
@@ -377,7 +378,7 @@ impl AssistantPanel {
                         cx.new_view(|cx| {
                             let keybind =
                                 KeyBinding::for_action_in(&DeployHistory, &focus_handle, cx);
-                            Tooltip::new("History").key_binding(keybind)
+                            Tooltip::new("Open History").key_binding(keybind)
                         })
                         .into()
                     })
@@ -1458,12 +1459,14 @@ impl WorkflowStepStatus {
                 .unwrap_or_default()
         }
         match self {
-            WorkflowStepStatus::Resolving => Icon::new(IconName::ArrowCircle)
-                .size(IconSize::Small)
+            WorkflowStepStatus::Resolving => Label::new("Resolving")
+                .size(LabelSize::Small)
                 .with_animation(
-                    ("resolving-suggestion-label", id),
-                    Animation::new(Duration::from_secs(2)).repeat(),
-                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
+                    ("resolving-suggestion-animation", id),
+                    Animation::new(Duration::from_secs(2))
+                        .repeat()
+                        .with_easing(pulsating_between(0.2, 1.0)),
+                    |label, delta| label.alpha(delta),
                 )
                 .into_any_element(),
 
@@ -1539,51 +1542,61 @@ impl WorkflowStepStatus {
                     }
                 })
                 .into_any_element(),
-            WorkflowStepStatus::Pending => Button::new(("stop-transformation", id), "Stop")
-                .icon(IconName::Stop)
-                .icon_position(IconPosition::Start)
-                .icon_size(IconSize::Small)
-                .label_size(LabelSize::Small)
-                .style(ButtonStyle::Tinted(TintColor::Negative))
-                .tooltip({
-                    let step_range = step_range.clone();
-                    let editor = editor.clone();
-                    move |cx| {
-                        cx.new_view(|cx| {
-                            let tooltip = Tooltip::new("Stop Transformation");
-                            if display_keybind_in_tooltip(&step_range, &editor, cx) {
-                                tooltip.key_binding(KeyBinding::for_action_in(
-                                    &editor::actions::Cancel,
-                                    &focus_handle,
-                                    cx,
-                                ))
-                            } else {
-                                tooltip
+            WorkflowStepStatus::Pending => h_flex()
+                .items_center()
+                .gap_2()
+                .child(
+                    Label::new("Applying...")
+                        .size(LabelSize::Small)
+                        .with_animation(
+                            ("applying-step-transformation-label", id),
+                            Animation::new(Duration::from_secs(2))
+                                .repeat()
+                                .with_easing(pulsating_between(0.2, 1.0)),
+                            |label, delta| label.alpha(delta),
+                        ),
+                )
+                .child(
+                    IconButton::new(("stop-transformation", id), IconName::Stop)
+                        .icon_size(IconSize::Small)
+                        .style(ButtonStyle::Tinted(TintColor::Negative))
+                        .tooltip({
+                            let step_range = step_range.clone();
+                            let editor = editor.clone();
+                            move |cx| {
+                                cx.new_view(|cx| {
+                                    let tooltip = Tooltip::new("Stop Transformation");
+                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
+                                        tooltip.key_binding(KeyBinding::for_action_in(
+                                            &editor::actions::Cancel,
+                                            &focus_handle,
+                                            cx,
+                                        ))
+                                    } else {
+                                        tooltip
+                                    }
+                                })
+                                .into()
                             }
                         })
-                        .into()
-                    }
-                })
-                .on_click({
-                    let editor = editor.clone();
-                    let step_range = step_range.clone();
-                    move |_, cx| {
-                        editor
-                            .update(cx, |this, cx| {
-                                this.stop_workflow_step(step_range.clone(), cx)
-                            })
-                            .ok();
-                    }
-                })
+                        .on_click({
+                            let editor = editor.clone();
+                            let step_range = step_range.clone();
+                            move |_, cx| {
+                                editor
+                                    .update(cx, |this, cx| {
+                                        this.stop_workflow_step(step_range.clone(), cx)
+                                    })
+                                    .ok();
+                            }
+                        }),
+                )
                 .into_any_element(),
             WorkflowStepStatus::Done => h_flex()
                 .gap_1()
                 .child(
-                    Button::new(("stop-transformation", id), "Reject")
-                        .icon(IconName::Close)
-                        .icon_position(IconPosition::Start)
+                    IconButton::new(("stop-transformation", id), IconName::Close)
                         .icon_size(IconSize::Small)
-                        .label_size(LabelSize::Small)
                         .style(ButtonStyle::Tinted(TintColor::Negative))
                         .tooltip({
                             let focus_handle = focus_handle.clone();
@@ -1664,7 +1677,6 @@ impl WorkflowStepStatus {
                         .icon_position(IconPosition::Start)
                         .icon_size(IconSize::Small)
                         .label_size(LabelSize::Small)
-                        .tooltip(|cx| Tooltip::text("Undo Transformation", cx))
                         .on_click({
                             let editor = editor.clone();
                             let step_range = step_range.clone();
@@ -2519,6 +2531,17 @@ impl ContextEditor {
                                     } else {
                                         theme.info_border
                                     };
+                                    let step_index = weak_self.update(&mut **cx, |this, cx| {
+                                       let snapshot = this.editor.read(cx).buffer().read(cx).as_singleton()?.read(cx).text_snapshot();
+                                       let start_offset = step_range.start.to_offset(&snapshot);
+                                       let parent_message = this.context.read(cx).messages_for_offsets([start_offset], cx);
+                                       debug_assert_eq!(parent_message.len(), 1);
+                                       let parent_message = parent_message.first()?;
+
+                                       let index_of_current_step = this.workflow_steps.keys().filter(|workflow_step_range| workflow_step_range.start.cmp(&parent_message.anchor, &snapshot).is_ge() && workflow_step_range.end.cmp(&step_range.end, &snapshot).is_le()).count();
+                                       Some(index_of_current_step)
+                                    }).ok().flatten();
+
                                     let debug_header = weak_self
                                         .update(&mut **cx, |this, _| {
                                             if let Some(inspector) = this.debug_inspector.as_mut() {
@@ -2528,6 +2551,17 @@ impl ContextEditor {
                                             }
                                         })
                                         .unwrap_or_default();
+                                    let step_label = if let Some(index) = step_index {
+
+                                        Label::new(format!("Step {index}")).size(LabelSize::Small)
+                                        } else {
+                                            Label::new("Step").size(LabelSize::Small)
+                                        };
+                                    let step_label = if current_status.as_ref().is_some_and(|status| status.is_confirmed()) {
+                                        h_flex().items_center().gap_2().child(step_label.strikethrough(true).color(Color::Muted)).child(Icon::new(IconName::Check).size(IconSize::Small).color(Color::Created))
+                                    } else {
+                                        div().child(step_label)
+                                    };
                                     div()
                                         .w_full()
                                         .px(cx.gutter_dimensions.full_width())
@@ -2536,11 +2570,12 @@ impl ContextEditor {
                                                 .w_full()
                                                 .border_b_1()
                                                 .border_color(border_color)
-                                                .pb_1()
+                                                .pb_1p5()
                                                 .justify_between()
                                                 .gap_2()
-                                                .children(debug_header.map(|is_active| {
-                                                    h_flex().justify_start().child(
+                                                .child(h_flex().justify_start().gap_2().child(step_label).children(
+                                                    debug_header.map(|is_active| {
+
                                                         Button::new("debug-workflows-toggle", "Debug")
                                                             .icon_color(Color::Hidden)
                                                             .color(Color::Hidden)
@@ -2571,10 +2606,10 @@ impl ContextEditor {
                                                                         })
                                                                         .ok();
                                                                 }
-                                                            }),
-                                                    )
-                                                    // .child(h_flex().w_full())
-                                                }))
+                                                            })
+                                                    })
+
+                                                ))
                                                 .children(current_status.as_ref().map(|status| {
                                                     h_flex().w_full().justify_end().child(
                                                         status.into_element(
@@ -2618,9 +2653,7 @@ impl ContextEditor {
                                 div()
                                     .w_full()
                                     .px(cx.gutter_dimensions.full_width())
-                                    .child(
-                                        h_flex().w_full().border_t_1().border_color(border_color),
-                                    )
+                                    .child(h_flex().h(px(1.)).bg(border_color))
                                     .into_any()
                             }),
                             disposition: BlockDisposition::Below,

crates/assistant/src/context.rs 🔗

@@ -1252,7 +1252,10 @@ impl Context {
 
             if let Some(step_end_index) = line.find("</step>") {
                 if in_step {
-                    let step_open_tag_end_ix = step_open_tag_start_ix + "<step>".len();
+                    let mut step_open_tag_end_ix = step_open_tag_start_ix + "<step>".len();
+                    if buffer.chars_at(step_open_tag_end_ix).next() == Some('\n') {
+                        step_open_tag_end_ix += 1;
+                    }
                     let mut step_end_tag_start_ix = line_start_offset + step_end_index;
                     let step_end_tag_end_ix = step_end_tag_start_ix + "</step>".len();
                     if buffer.reversed_chars_at(step_end_tag_start_ix).next() == Some('\n') {
@@ -3098,12 +3101,12 @@ mod tests {
                 vec![
                     (
                         Point::new(response_start_row + 2, 0)
-                            ..Point::new(response_start_row + 13, 3),
+                            ..Point::new(response_start_row + 12, 3),
                         WorkflowStepTestStatus::Pending
                     ),
                     (
-                        Point::new(response_start_row + 15, 0)
-                            ..Point::new(response_start_row + 26, 3),
+                        Point::new(response_start_row + 14, 0)
+                            ..Point::new(response_start_row + 24, 3),
                         WorkflowStepTestStatus::Pending
                     ),
                 ]
@@ -3135,12 +3138,12 @@ mod tests {
                 vec![
                     (
                         Point::new(response_start_row + 2, 0)
-                            ..Point::new(response_start_row + 13, 3),
+                            ..Point::new(response_start_row + 12, 3),
                         WorkflowStepTestStatus::Resolved
                     ),
                     (
-                        Point::new(response_start_row + 15, 0)
-                            ..Point::new(response_start_row + 26, 3),
+                        Point::new(response_start_row + 14, 0)
+                            ..Point::new(response_start_row + 24, 3),
                         WorkflowStepTestStatus::Pending
                     ),
                 ]

crates/assistant/src/context_inspector.rs 🔗

@@ -8,7 +8,7 @@ use editor::{
 use gpui::{AppContext, Model, View};
 use text::{ToOffset, ToPoint};
 use ui::{
-    div, h_flex, Color, Element as _, ParentElement as _, Styled, ViewContext, WindowContext,
+    div, h_flex, px, Color, Element as _, ParentElement as _, Styled, ViewContext, WindowContext,
 };
 
 use crate::{Context, ResolvedWorkflowStep, WorkflowSuggestion};
@@ -109,12 +109,7 @@ impl ContextInspector {
                             div()
                                 .w_full()
                                 .px(cx.gutter_dimensions.full_width())
-                                .child(
-                                    h_flex()
-                                        .w_full()
-                                        .border_t_1()
-                                        .border_color(Color::Warning.color(cx)),
-                                )
+                                .child(h_flex().h(px(1.)).bg(Color::Warning.color(cx)))
                                 .into_any()
                         }),
                         disposition: BlockDisposition::Below,

crates/ui/src/components/icon.rs 🔗

@@ -254,7 +254,6 @@ pub enum IconName {
     Tab,
     Terminal,
     TextCursor,
-    TextSearch,
     Trash,
     TriangleRight,
     Undo,
@@ -418,7 +417,6 @@ impl IconName {
             IconName::Tab => "icons/tab.svg",
             IconName::Terminal => "icons/terminal.svg",
             IconName::TextCursor => "icons/text-cursor.svg",
-            IconName::TextSearch => "icons/text-search.svg",
             IconName::Trash => "icons/trash.svg",
             IconName::TriangleRight => "icons/triangle_right.svg",
             IconName::Update => "icons/update.svg",