assistant2: Agent notification improvements (#27638)
- Show thread's summary in notification title - Improve thread's summary prompt so it's more descriptive - Make whole notification clickable  Release Notes: - N/A
This commit is contained in:
parent
82ce187bfc
commit
da47013e56
4 changed files with 156 additions and 108 deletions
|
@ -4,7 +4,7 @@ use crate::thread::{
|
||||||
};
|
};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus};
|
use crate::tool_use::{PendingToolUseStatus, ToolUse, ToolUseStatus};
|
||||||
use crate::ui::{ContextPill, ToolReadyPopUp, ToolReadyPopupEvent};
|
use crate::ui::{AgentNotification, AgentNotificationEvent, ContextPill};
|
||||||
use crate::AssistantPanel;
|
use crate::AssistantPanel;
|
||||||
use assistant_settings::AssistantSettings;
|
use assistant_settings::AssistantSettings;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
|
@ -45,9 +45,9 @@ pub struct ActiveThread {
|
||||||
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
|
expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
|
||||||
expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
|
expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
|
||||||
last_error: Option<ThreadError>,
|
last_error: Option<ThreadError>,
|
||||||
pop_ups: Vec<WindowHandle<ToolReadyPopUp>>,
|
notifications: Vec<WindowHandle<AgentNotification>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
pop_up_subscriptions: HashMap<WindowHandle<ToolReadyPopUp>, Vec<Subscription>>,
|
notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderedMessage {
|
struct RenderedMessage {
|
||||||
|
@ -252,9 +252,9 @@ impl ActiveThread {
|
||||||
scrollbar_state: ScrollbarState::new(list_state),
|
scrollbar_state: ScrollbarState::new(list_state),
|
||||||
editing_message: None,
|
editing_message: None,
|
||||||
last_error: None,
|
last_error: None,
|
||||||
pop_ups: Vec::new(),
|
notifications: Vec::new(),
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
pop_up_subscriptions: HashMap::default(),
|
notification_subscriptions: HashMap::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
|
||||||
|
@ -377,24 +377,23 @@ impl ActiveThread {
|
||||||
self.save_thread(cx);
|
self.save_thread(cx);
|
||||||
}
|
}
|
||||||
ThreadEvent::DoneStreaming => {
|
ThreadEvent::DoneStreaming => {
|
||||||
if !self.thread().read(cx).is_generating() {
|
let thread = self.thread.read(cx);
|
||||||
|
|
||||||
|
if !thread.is_generating() {
|
||||||
self.show_notification(
|
self.show_notification(
|
||||||
"The assistant response has concluded.",
|
if thread.used_tools_since_last_user_message() {
|
||||||
IconName::Check,
|
"Finished running tools"
|
||||||
Color::Success,
|
} else {
|
||||||
|
"New message"
|
||||||
|
},
|
||||||
|
IconName::ZedAssistant,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ThreadEvent::ToolConfirmationNeeded => {
|
ThreadEvent::ToolConfirmationNeeded => {
|
||||||
self.show_notification(
|
self.show_notification("Waiting for tool confirmation", IconName::Info, window, cx);
|
||||||
"There's a tool confirmation needed.",
|
|
||||||
IconName::Info,
|
|
||||||
Color::Muted,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
ThreadEvent::StreamedAssistantText(message_id, text) => {
|
||||||
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
|
if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
|
||||||
|
@ -526,85 +525,90 @@ impl ActiveThread {
|
||||||
&mut self,
|
&mut self,
|
||||||
caption: impl Into<SharedString>,
|
caption: impl Into<SharedString>,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
icon_color: Color,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<'_, ActiveThread>,
|
cx: &mut Context<'_, ActiveThread>,
|
||||||
) {
|
) {
|
||||||
if !window.is_window_active()
|
if window.is_window_active()
|
||||||
&& self.pop_ups.is_empty()
|
|| !self.notifications.is_empty()
|
||||||
&& AssistantSettings::get_global(cx).notify_when_agent_waiting
|
|| !AssistantSettings::get_global(cx).notify_when_agent_waiting
|
||||||
{
|
{
|
||||||
let caption = caption.into();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for screen in cx.displays() {
|
let caption = caption.into();
|
||||||
let options = ToolReadyPopUp::window_options(screen, cx);
|
|
||||||
|
|
||||||
if let Some(screen_window) = cx
|
let title = self
|
||||||
.open_window(options, |_, cx| {
|
.thread
|
||||||
cx.new(|_| ToolReadyPopUp::new(caption.clone(), icon, icon_color))
|
.read(cx)
|
||||||
})
|
.summary()
|
||||||
.log_err()
|
.unwrap_or("Agent Panel".into());
|
||||||
{
|
|
||||||
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();
|
for screen in cx.displays() {
|
||||||
|
let options = AgentNotification::window_options(screen, cx);
|
||||||
|
|
||||||
// If there are multiple Zed windows, activate the correct one.
|
if let Some(screen_window) = cx
|
||||||
cx.defer(move |cx| {
|
.open_window(options, |_, cx| {
|
||||||
handle
|
cx.new(|_| AgentNotification::new(title.clone(), caption.clone(), icon))
|
||||||
.update(cx, |_view, window, _cx| {
|
})
|
||||||
window.activate_window();
|
.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
|
||||||
|
|
||||||
if let Some(workspace) =
|
let workspace_handle = this.workspace.clone();
|
||||||
workspace_handle.upgrade()
|
|
||||||
{
|
// If there are multiple Zed windows, activate the correct one.
|
||||||
workspace.update(_cx, |workspace, cx| {
|
cx.defer(move |cx| {
|
||||||
workspace
|
handle
|
||||||
.focus_panel::<AssistantPanel>(
|
.update(cx, |_view, window, _cx| {
|
||||||
window, cx,
|
window.activate_window();
|
||||||
);
|
|
||||||
});
|
if let Some(workspace) = workspace_handle.upgrade()
|
||||||
}
|
{
|
||||||
})
|
workspace.update(_cx, |workspace, cx| {
|
||||||
.log_err();
|
workspace.focus_panel::<AssistantPanel>(
|
||||||
|
window, cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dismiss_notifications(cx);
|
||||||
|
}
|
||||||
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dismiss_notifications(cx);
|
|
||||||
}
|
|
||||||
ToolReadyPopupEvent::Dismissed => {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1764,14 +1768,14 @@ impl ActiveThread {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismiss_notifications(&mut self, cx: &mut Context<'_, ActiveThread>) {
|
fn dismiss_notifications(&mut self, cx: &mut Context<'_, ActiveThread>) {
|
||||||
for window in self.pop_ups.drain(..) {
|
for window in self.notifications.drain(..) {
|
||||||
window
|
window
|
||||||
.update(cx, |_, window, _| {
|
.update(cx, |_, window, _| {
|
||||||
window.remove_window();
|
window.remove_window();
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
self.pop_up_subscriptions.remove(&window);
|
self.notification_subscriptions.remove(&window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -786,6 +786,18 @@ impl Thread {
|
||||||
self.stream_completion(request, model, cx);
|
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(
|
pub fn to_completion_request(
|
||||||
&self,
|
&self,
|
||||||
request_kind: RequestKind,
|
request_kind: RequestKind,
|
||||||
|
@ -835,6 +847,9 @@ impl Thread {
|
||||||
}
|
}
|
||||||
RequestKind::Summarize => {
|
RequestKind::Summarize => {
|
||||||
// We don't care about tool use during summarization.
|
// 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 {
|
request.messages.push(LanguageModelRequestMessage {
|
||||||
role: Role::User,
|
role: Role::User,
|
||||||
content: vec![
|
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(),
|
.into(),
|
||||||
],
|
],
|
||||||
cache: false,
|
cache: false,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
mod agent_notification;
|
||||||
mod context_pill;
|
mod context_pill;
|
||||||
mod tool_ready_pop_up;
|
|
||||||
|
|
||||||
|
pub use agent_notification::*;
|
||||||
pub use context_pill::*;
|
pub use context_pill::*;
|
||||||
pub use tool_ready_pop_up::*;
|
|
||||||
|
|
|
@ -1,24 +1,29 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
point, App, Context, EventEmitter, IntoElement, PlatformDisplay, Size, Window,
|
linear_color_stop, linear_gradient, point, App, Context, EventEmitter, IntoElement,
|
||||||
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions,
|
PlatformDisplay, Size, Window, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||||
|
WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
use release_channel::ReleaseChannel;
|
use release_channel::ReleaseChannel;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use theme;
|
use theme;
|
||||||
use ui::{prelude::*, Render};
|
use ui::{prelude::*, Render};
|
||||||
|
|
||||||
pub struct ToolReadyPopUp {
|
pub struct AgentNotification {
|
||||||
|
title: SharedString,
|
||||||
caption: SharedString,
|
caption: SharedString,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
icon_color: Color,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToolReadyPopUp {
|
impl AgentNotification {
|
||||||
pub fn new(caption: impl Into<SharedString>, icon: IconName, icon_color: Color) -> Self {
|
pub fn new(
|
||||||
|
title: impl Into<SharedString>,
|
||||||
|
caption: impl Into<SharedString>,
|
||||||
|
icon: IconName,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
title: title.into(),
|
||||||
caption: caption.into(),
|
caption: caption.into(),
|
||||||
icon,
|
icon,
|
||||||
icon_color,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,19 +63,22 @@ impl ToolReadyPopUp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ToolReadyPopupEvent {
|
pub enum AgentNotificationEvent {
|
||||||
Accepted,
|
Accepted,
|
||||||
Dismissed,
|
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 {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let ui_font = theme::setup_ui_font(window, cx);
|
let ui_font = theme::setup_ui_font(window, cx);
|
||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
|
|
||||||
|
let bg = cx.theme().colors().elevated_surface_background;
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
|
.id("agent-notification")
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_3()
|
.p_3()
|
||||||
.gap_4()
|
.gap_4()
|
||||||
|
@ -80,14 +88,18 @@ impl Render for ToolReadyPopUp {
|
||||||
.font(ui_font)
|
.font(ui_font)
|
||||||
.border_color(cx.theme().colors().border)
|
.border_color(cx.theme().colors().border)
|
||||||
.rounded_xl()
|
.rounded_xl()
|
||||||
|
.on_click(cx.listener(|_, _, _, cx| {
|
||||||
|
cx.emit(AgentNotificationEvent::Accepted);
|
||||||
|
}))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_start()
|
.items_start()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
.child(
|
.child(
|
||||||
h_flex().h(line_height).justify_center().child(
|
h_flex().h(line_height).justify_center().child(
|
||||||
Icon::new(self.icon)
|
Icon::new(self.icon)
|
||||||
.color(self.icon_color)
|
.color(Color::Muted)
|
||||||
.size(IconSize::Small),
|
.size(IconSize::Small),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -95,33 +107,47 @@ impl Render for ToolReadyPopUp {
|
||||||
v_flex()
|
v_flex()
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(16.))
|
.text_size(px(14.))
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.child("Agent Panel"),
|
.child(self.title.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.text_size(px(14.))
|
.text_size(px(12.))
|
||||||
.text_color(cx.theme().colors().text_muted)
|
.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(
|
.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.gap_0p5()
|
.gap_1()
|
||||||
|
.items_center()
|
||||||
.child(
|
.child(
|
||||||
Button::new("open", "View Panel")
|
Button::new("open", "View Panel")
|
||||||
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
|
||||||
|
.full_width()
|
||||||
.on_click({
|
.on_click({
|
||||||
cx.listener(move |_this, _event, _, cx| {
|
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.listener(move |_, _event, _, cx| {
|
||||||
cx.emit(ToolReadyPopupEvent::Dismissed);
|
cx.emit(AgentNotificationEvent::Dismissed);
|
||||||
})
|
})
|
||||||
})),
|
})),
|
||||||
)
|
)
|
Loading…
Add table
Add a link
Reference in a new issue