diff --git a/crates/assistant2/src/active_thread.rs b/crates/assistant2/src/active_thread.rs index 46d037c296d226808f43047240a734c02e808cc1..adf8c1ad0235b7a32d31a354f448fdd8e9bd94d5 100644 --- a/crates/assistant2/src/active_thread.rs +++ b/crates/assistant2/src/active_thread.rs @@ -4,7 +4,7 @@ use crate::thread::{ }; use crate::thread_store::ThreadStore; use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus}; -use crate::ui::{ContextPill, ToolReadyPopUp, ToolReadyPopupEvent}; +use crate::ui::{AgentNotification, AgentNotificationEvent, ContextPill}; use crate::AssistantPanel; use assistant_settings::AssistantSettings; use collections::HashMap; @@ -45,9 +45,9 @@ pub struct ActiveThread { expanded_tool_uses: HashMap, expanded_thinking_segments: HashMap<(MessageId, usize), bool>, last_error: Option, - pop_ups: Vec>, + notifications: Vec>, _subscriptions: Vec, - pop_up_subscriptions: HashMap, Vec>, + notification_subscriptions: HashMap, Vec>, } struct RenderedMessage { @@ -252,9 +252,9 @@ impl ActiveThread { scrollbar_state: ScrollbarState::new(list_state), editing_message: None, last_error: None, - pop_ups: Vec::new(), + notifications: Vec::new(), _subscriptions: subscriptions, - pop_up_subscriptions: HashMap::default(), + notification_subscriptions: HashMap::default(), }; for message in thread.read(cx).messages().cloned().collect::>() { @@ -377,24 +377,23 @@ impl ActiveThread { self.save_thread(cx); } ThreadEvent::DoneStreaming => { - if !self.thread().read(cx).is_generating() { + let thread = self.thread.read(cx); + + if !thread.is_generating() { self.show_notification( - "The assistant response has concluded.", - IconName::Check, - Color::Success, + if thread.used_tools_since_last_user_message() { + "Finished running tools" + } else { + "New message" + }, + IconName::ZedAssistant, window, cx, ); } } ThreadEvent::ToolConfirmationNeeded => { - self.show_notification( - "There's a tool confirmation needed.", - IconName::Info, - Color::Muted, - window, - cx, - ); + self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx); } ThreadEvent::StreamedAssistantText(message_id, text) => { if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) { @@ -526,85 +525,90 @@ impl ActiveThread { &mut self, caption: impl Into, icon: IconName, - icon_color: Color, window: &mut Window, cx: &mut Context<'_, ActiveThread>, ) { - if !window.is_window_active() - && self.pop_ups.is_empty() - && AssistantSettings::get_global(cx).notify_when_agent_waiting + if window.is_window_active() + || !self.notifications.is_empty() + || !AssistantSettings::get_global(cx).notify_when_agent_waiting { - let caption = caption.into(); + return; + } - for screen in cx.displays() { - let options = ToolReadyPopUp::window_options(screen, cx); + let caption = caption.into(); - if let Some(screen_window) = cx - .open_window(options, |_, cx| { - cx.new(|_| ToolReadyPopUp::new(caption.clone(), icon, icon_color)) - }) - .log_err() - { - if let Some(pop_up) = screen_window.entity(cx).log_err() { - self.pop_up_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push(cx.subscribe_in(&pop_up, window, { - |this, _, event, window, cx| match event { - ToolReadyPopupEvent::Accepted => { - let handle = window.window_handle(); - cx.activate(true); // Switch back to the Zed application - - let workspace_handle = this.workspace.clone(); - - // If there are multiple Zed windows, activate the correct one. - cx.defer(move |cx| { - handle - .update(cx, |_view, window, _cx| { - window.activate_window(); - - if let Some(workspace) = - workspace_handle.upgrade() - { - workspace.update(_cx, |workspace, cx| { - workspace - .focus_panel::( - window, cx, - ); - }); - } - }) - .log_err(); - }); + let title = self + .thread + .read(cx) + .summary() + .unwrap_or("Agent Panel".into()); - this.dismiss_notifications(cx); - } - ToolReadyPopupEvent::Dismissed => { - this.dismiss_notifications(cx); - } + for screen in cx.displays() { + let options = AgentNotification::window_options(screen, cx); + + if let Some(screen_window) = cx + .open_window(options, |_, cx| { + cx.new(|_| AgentNotification::new(title.clone(), caption.clone(), icon)) + }) + .log_err() + { + if let Some(pop_up) = screen_window.entity(cx).log_err() { + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push(cx.subscribe_in(&pop_up, window, { + |this, _, event, window, cx| match event { + AgentNotificationEvent::Accepted => { + let handle = window.window_handle(); + cx.activate(true); // Switch back to the Zed application + + let workspace_handle = this.workspace.clone(); + + // If there are multiple Zed windows, activate the correct one. + cx.defer(move |cx| { + handle + .update(cx, |_view, window, _cx| { + window.activate_window(); + + if let Some(workspace) = workspace_handle.upgrade() + { + workspace.update(_cx, |workspace, cx| { + workspace.focus_panel::( + window, cx, + ); + }); + } + }) + .log_err(); + }); + + this.dismiss_notifications(cx); } - })); - - self.pop_ups.push(screen_window); - - // If the user manually refocuses the original window, dismiss the popup. - self.pop_up_subscriptions - .entry(screen_window) - .or_insert_with(Vec::new) - .push({ - let pop_up_weak = pop_up.downgrade(); - - cx.observe_window_activation(window, move |_, window, cx| { - if window.is_window_active() { - if let Some(pop_up) = pop_up_weak.upgrade() { - pop_up.update(cx, |_, cx| { - cx.emit(ToolReadyPopupEvent::Dismissed); - }); - } + AgentNotificationEvent::Dismissed => { + this.dismiss_notifications(cx); + } + } + })); + + self.notifications.push(screen_window); + + // If the user manually refocuses the original window, dismiss the popup. + self.notification_subscriptions + .entry(screen_window) + .or_insert_with(Vec::new) + .push({ + let pop_up_weak = pop_up.downgrade(); + + cx.observe_window_activation(window, move |_, window, cx| { + if window.is_window_active() { + if let Some(pop_up) = pop_up_weak.upgrade() { + pop_up.update(cx, |_, cx| { + cx.emit(AgentNotificationEvent::Dismissed); + }); } - }) - }); - } + } + }) + }); } } } @@ -1764,14 +1768,14 @@ impl ActiveThread { } fn dismiss_notifications(&mut self, cx: &mut Context<'_, ActiveThread>) { - for window in self.pop_ups.drain(..) { + for window in self.notifications.drain(..) { window .update(cx, |_, window, _| { window.remove_window(); }) .ok(); - self.pop_up_subscriptions.remove(&window); + self.notification_subscriptions.remove(&window); } } diff --git a/crates/assistant2/src/thread.rs b/crates/assistant2/src/thread.rs index 5bcf6714ba5f32fbee697ac653859b871362fbe0..71ff71a9013c774a8ace2d24907d17b54c92ac16 100644 --- a/crates/assistant2/src/thread.rs +++ b/crates/assistant2/src/thread.rs @@ -786,6 +786,18 @@ impl Thread { self.stream_completion(request, model, cx); } + pub fn used_tools_since_last_user_message(&self) -> bool { + for message in self.messages.iter().rev() { + if self.tool_use.message_has_tool_results(message.id) { + return true; + } else if message.role == Role::User { + return false; + } + } + + false + } + pub fn to_completion_request( &self, request_kind: RequestKind, @@ -835,6 +847,9 @@ impl Thread { } RequestKind::Summarize => { // We don't care about tool use during summarization. + if self.tool_use.message_has_tool_results(message.id) { + continue; + } } } @@ -1126,7 +1141,10 @@ impl Thread { request.messages.push(LanguageModelRequestMessage { role: Role::User, content: vec![ - "Generate a concise 3-7 word title for this conversation, omitting punctuation. Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`" + "Generate a concise 3-7 word title for this conversation, omitting punctuation. \ + Go straight to the title, without any preamble and prefix like `Here's a concise suggestion:...` or `Title:`. \ + If the conversation is about a specific subject, include it in the title. \ + Be descriptive. DO NOT speak in the first person." .into(), ], cache: false, diff --git a/crates/assistant2/src/ui.rs b/crates/assistant2/src/ui.rs index 390a6f8edc93f8bafac4653d66896cef6802e3a3..40a84863af3072ba9db3de8e6d1089baece74342 100644 --- a/crates/assistant2/src/ui.rs +++ b/crates/assistant2/src/ui.rs @@ -1,5 +1,5 @@ +mod agent_notification; mod context_pill; -mod tool_ready_pop_up; +pub use agent_notification::*; pub use context_pill::*; -pub use tool_ready_pop_up::*; diff --git a/crates/assistant2/src/ui/tool_ready_pop_up.rs b/crates/assistant2/src/ui/agent_notification.rs similarity index 61% rename from crates/assistant2/src/ui/tool_ready_pop_up.rs rename to crates/assistant2/src/ui/agent_notification.rs index ca9af802a3cb79134bcbd2a95d7717ffbafcc6c2..4f2025edb778b594c4fb7d5c7bc8e4f60c183e83 100644 --- a/crates/assistant2/src/ui/tool_ready_pop_up.rs +++ b/crates/assistant2/src/ui/agent_notification.rs @@ -1,24 +1,29 @@ use gpui::{ - point, App, Context, EventEmitter, IntoElement, PlatformDisplay, Size, Window, - WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, + linear_color_stop, linear_gradient, point, App, Context, EventEmitter, IntoElement, + PlatformDisplay, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations, + WindowKind, WindowOptions, }; use release_channel::ReleaseChannel; use std::rc::Rc; use theme; use ui::{prelude::*, Render}; -pub struct ToolReadyPopUp { +pub struct AgentNotification { + title: SharedString, caption: SharedString, icon: IconName, - icon_color: Color, } -impl ToolReadyPopUp { - pub fn new(caption: impl Into, icon: IconName, icon_color: Color) -> Self { +impl AgentNotification { + pub fn new( + title: impl Into, + caption: impl Into, + icon: IconName, + ) -> Self { Self { + title: title.into(), caption: caption.into(), icon, - icon_color, } } @@ -58,19 +63,22 @@ impl ToolReadyPopUp { } } -pub enum ToolReadyPopupEvent { +pub enum AgentNotificationEvent { Accepted, Dismissed, } -impl EventEmitter for ToolReadyPopUp {} +impl EventEmitter for AgentNotification {} -impl Render for ToolReadyPopUp { +impl Render for AgentNotification { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let ui_font = theme::setup_ui_font(window, cx); let line_height = window.line_height(); + let bg = cx.theme().colors().elevated_surface_background; + h_flex() + .id("agent-notification") .size_full() .p_3() .gap_4() @@ -80,14 +88,18 @@ impl Render for ToolReadyPopUp { .font(ui_font) .border_color(cx.theme().colors().border) .rounded_xl() + .on_click(cx.listener(|_, _, _, cx| { + cx.emit(AgentNotificationEvent::Accepted); + })) .child( h_flex() .items_start() .gap_2() + .flex_1() .child( h_flex().h(line_height).justify_center().child( Icon::new(self.icon) - .color(self.icon_color) + .color(Color::Muted) .size(IconSize::Small), ), ) @@ -95,33 +107,47 @@ impl Render for ToolReadyPopUp { v_flex() .child( div() - .text_size(px(16.)) + .text_size(px(14.)) .text_color(cx.theme().colors().text) - .child("Agent Panel"), + .child(self.title.clone()), ) .child( div() - .text_size(px(14.)) + .text_size(px(12.)) .text_color(cx.theme().colors().text_muted) - .child(self.caption.clone()), + .max_w(px(340.)) + .truncate() + .child(self.caption.clone()) + .relative() + .child( + div().h_full().absolute().w_8().bottom_0().right_0().bg( + linear_gradient( + 90., + linear_color_stop(bg, 1.), + linear_color_stop(bg.opacity(0.2), 0.), + ), + ), + ), ), ), ) .child( - h_flex() - .gap_0p5() + v_flex() + .gap_1() + .items_center() .child( Button::new("open", "View Panel") .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .full_width() .on_click({ cx.listener(move |_this, _event, _, cx| { - cx.emit(ToolReadyPopupEvent::Accepted); + cx.emit(AgentNotificationEvent::Accepted); }) }), ) - .child(Button::new("dismiss", "Dismiss").on_click({ + .child(Button::new("dismiss", "Dismiss").full_width().on_click({ cx.listener(move |_, _event, _, cx| { - cx.emit(ToolReadyPopupEvent::Dismissed); + cx.emit(AgentNotificationEvent::Dismissed); }) })), )