diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index d04ae6e3b7..9e49760394 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -34,9 +34,9 @@ use fs::Fs; use gpui::{ div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter, - FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels, - Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, - UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext, + FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, + Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, + Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext, }; use indexed_docs::IndexedDocsStore; use language::{ @@ -1316,6 +1316,7 @@ pub struct ContextEditor { _subscriptions: Vec, active_edit_step: Option, assistant_panel: WeakView, + error_message: Option, } const DEFAULT_TAB_TITLE: &str = "New Context"; @@ -1373,6 +1374,7 @@ impl ContextEditor { _subscriptions, active_edit_step: None, assistant_panel, + error_message: None, }; this.update_message_headers(cx); this.insert_slash_command_output_sections(sections, cx); @@ -1406,7 +1408,9 @@ impl ContextEditor { fn assist(&mut self, _: &Assist, cx: &mut ViewContext) { if !self.apply_edit_step(cx) { + self.error_message = None; self.send_to_model(cx); + cx.notify(); } } @@ -1804,6 +1808,9 @@ impl ContextEditor { } } ContextEvent::Operation(_) => {} + ContextEvent::AssistError(error_message) => { + self.error_message = Some(SharedString::from(error_message.clone())); + } } } @@ -2142,7 +2149,10 @@ impl ContextEditor { div() .id("error") .tooltip(move |cx| Tooltip::text(error.clone(), cx)) - .child(Icon::new(IconName::XCircle)), + .child( + Icon::new(IconName::ExclamationTriangle) + .color(Color::Error), + ), ) } else { None @@ -2327,25 +2337,33 @@ impl ContextEditor { .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE)) } - fn render_notice(&self, cx: &mut ViewContext) -> Option { + fn dismiss_error_message(&mut self, cx: &mut ViewContext) { + self.error_message = None; + cx.notify(); + } + + fn render_notice(&self, cx: &mut ViewContext) -> Option { let nudge = self .assistant_panel .upgrade() .map(|assistant_panel| assistant_panel.read(cx).show_zed_ai_notice); - if nudge.unwrap_or(false) { + if let Some(error) = self.error_message.clone() { + Some(Self::render_error_popover(error, cx).into_any_element()) + } else if nudge.unwrap_or(false) { Some( v_flex() .elevation_3(cx) - .p_4() + .p_2() .gap_2() - .child(Label::new("Use Zed AI")) .child( - div() - .id("sign-in") - .child(Label::new("Sign in to use Zed AI")) - .cursor_pointer() - .on_click(cx.listener(|this, _event, cx| { + Label::new("Use Zed AI") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .child(h_flex().justify_end().child( + Button::new("sign-in", "Sign in to use Zed AI").on_click(cx.listener( + |this, _event, cx| { let client = this .workspace .update(cx, |workspace, _| workspace.client().clone()) @@ -2358,8 +2376,10 @@ impl ContextEditor { }) .detach_and_log_err(cx) } - })), - ), + }, + )), + )) + .into_any_element(), ) } else if let Some(configuration_error) = configuration_error(cx) { let label = match configuration_error { @@ -2369,27 +2389,51 @@ impl ContextEditor { Some( v_flex() .elevation_3(cx) - .p_4() + .p_2() .gap_2() - .child(Label::new(label)) + .child(Label::new(label).size(LabelSize::Small).color(Color::Muted)) .child( - div() - .id("open-configuration") - .child(Label::new("Open configuration")) - .cursor_pointer() - .on_click({ - let focus_handle = self.focus_handle(cx).clone(); - move |_event, cx| { - focus_handle.dispatch_action(&ShowConfiguration, cx); - } - }), - ), + h_flex().justify_end().child( + Button::new("open-configuration", "Open configuration") + .icon(IconName::Settings) + .icon_size(IconSize::Small) + .on_click({ + let focus_handle = self.focus_handle(cx).clone(); + move |_event, cx| { + focus_handle.dispatch_action(&ShowConfiguration, cx); + } + }), + ), + ) + .into_any_element(), ) } else { None } } + fn render_error_popover(error: SharedString, cx: &mut ViewContext) -> Div { + v_flex() + .p_2() + .elevation_2(cx) + .bg(cx.theme().colors().surface_background) + .min_w_24() + .occlude() + .child( + Label::new("Error interacting with language model") + .size(LabelSize::Small) + .weight(FontWeight::BOLD) + .color(Color::Muted), + ) + .child(Label::new(error).size(LabelSize::Small)) + .child( + h_flex().justify_end().child( + Button::new("dismiss", "Dismiss") + .on_click(cx.listener(|this, _, cx| this.dismiss_error_message(cx))), + ), + ) + } + fn render_send_button(&self, cx: &mut ViewContext) -> impl IntoElement { let focus_handle = self.focus_handle(cx).clone(); let button_text = match self.workflow_step_for_cursor(cx) { @@ -2487,23 +2531,32 @@ impl Render for ContextEditor { div() .flex_grow() .bg(cx.theme().colors().editor_background) - .child(self.editor.clone()) + .child(self.editor.clone()), + ) + .child( + h_flex() + .flex_none() + .relative() + .when_some(self.render_notice(cx), |this, notice| { + this.child( + div() + .absolute() + .w_3_4() + .min_w_24() + .max_w_128() + .right_4() + .bottom_9() + .child(notice), + ) + }) .child( h_flex() .w_full() .absolute() - .bottom_0() - .p_4() + .right_4() + .bottom_2() .justify_end() - .child( - v_flex() - .gap_2() - .items_end() - .when_some(self.render_notice(cx), |this, notice| { - this.child(notice) - }) - .child(self.render_send_button(cx)), - ), + .child(self.render_send_button(cx)), ), ) } diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index 80178a8342..7120e983b6 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant/src/context.rs @@ -281,6 +281,7 @@ impl ContextOperation { #[derive(Debug, Clone)] pub enum ContextEvent { + AssistError(String), MessagesEdited, SummaryChanged, EditStepsChanged, @@ -1580,6 +1581,10 @@ impl Context { .err() .map(|error| error.to_string().trim().to_string()); + if let Some(error_message) = error_message.as_ref() { + cx.emit(ContextEvent::AssistError(error_message.to_string())); + } + this.update_metadata(assistant_message_id, cx, |metadata| { if let Some(error_message) = error_message.as_ref() { metadata.status =