@@ -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<LanguageModelToolUseId, bool>,
expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
last_error: Option<ThreadError>,
- pop_ups: Vec<WindowHandle<ToolReadyPopUp>>,
+ notifications: Vec<WindowHandle<AgentNotification>>,
_subscriptions: Vec<Subscription>,
- pop_up_subscriptions: HashMap<WindowHandle<ToolReadyPopUp>, Vec<Subscription>>,
+ notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
}
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::<Vec<_>>() {
@@ -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<SharedString>,
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::<AssistantPanel>(
- 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::<AssistantPanel>(
+ 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);
}
}
@@ -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,
@@ -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<SharedString>, icon: IconName, icon_color: Color) -> Self {
+impl AgentNotification {
+ pub fn new(
+ title: impl Into<SharedString>,
+ caption: impl Into<SharedString>,
+ 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<ToolReadyPopupEvent> for ToolReadyPopUp {}
+impl EventEmitter<AgentNotificationEvent> for AgentNotification {}
-impl Render for ToolReadyPopUp {
+impl Render for AgentNotification {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> 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);
})
})),
)