assistant: Show error messages in popover notification (#15808)
<img width="644" alt="image" src="https://github.com/user-attachments/assets/ee6f2e60-e50a-481e-98b4-6c4b72a9b882"> Release Notes: - N/A --------- Co-authored-by: Thorsten <thorsten@zed.dev>
This commit is contained in:
parent
294892c470
commit
be0ccf47ee
2 changed files with 98 additions and 40 deletions
|
@ -34,9 +34,9 @@ use fs::Fs;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
|
||||||
AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
|
AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
|
||||||
FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ParentElement, Pixels,
|
FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement,
|
||||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||||
UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
|
Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use indexed_docs::IndexedDocsStore;
|
use indexed_docs::IndexedDocsStore;
|
||||||
use language::{
|
use language::{
|
||||||
|
@ -1316,6 +1316,7 @@ pub struct ContextEditor {
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
active_edit_step: Option<ActiveEditStep>,
|
active_edit_step: Option<ActiveEditStep>,
|
||||||
assistant_panel: WeakView<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
|
error_message: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_TAB_TITLE: &str = "New Context";
|
const DEFAULT_TAB_TITLE: &str = "New Context";
|
||||||
|
@ -1373,6 +1374,7 @@ impl ContextEditor {
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
active_edit_step: None,
|
active_edit_step: None,
|
||||||
assistant_panel,
|
assistant_panel,
|
||||||
|
error_message: None,
|
||||||
};
|
};
|
||||||
this.update_message_headers(cx);
|
this.update_message_headers(cx);
|
||||||
this.insert_slash_command_output_sections(sections, cx);
|
this.insert_slash_command_output_sections(sections, cx);
|
||||||
|
@ -1406,7 +1408,9 @@ impl ContextEditor {
|
||||||
|
|
||||||
fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
|
fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
|
||||||
if !self.apply_edit_step(cx) {
|
if !self.apply_edit_step(cx) {
|
||||||
|
self.error_message = None;
|
||||||
self.send_to_model(cx);
|
self.send_to_model(cx);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1804,6 +1808,9 @@ impl ContextEditor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContextEvent::Operation(_) => {}
|
ContextEvent::Operation(_) => {}
|
||||||
|
ContextEvent::AssistError(error_message) => {
|
||||||
|
self.error_message = Some(SharedString::from(error_message.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2142,7 +2149,10 @@ impl ContextEditor {
|
||||||
div()
|
div()
|
||||||
.id("error")
|
.id("error")
|
||||||
.tooltip(move |cx| Tooltip::text(error.clone(), cx))
|
.tooltip(move |cx| Tooltip::text(error.clone(), cx))
|
||||||
.child(Icon::new(IconName::XCircle)),
|
.child(
|
||||||
|
Icon::new(IconName::ExclamationTriangle)
|
||||||
|
.color(Color::Error),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -2327,25 +2337,33 @@ impl ContextEditor {
|
||||||
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
|
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
fn dismiss_error_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
self.error_message = None;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||||
let nudge = self
|
let nudge = self
|
||||||
.assistant_panel
|
.assistant_panel
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.map(|assistant_panel| assistant_panel.read(cx).show_zed_ai_notice);
|
.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(
|
Some(
|
||||||
v_flex()
|
v_flex()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.p_4()
|
.p_2()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Label::new("Use Zed AI"))
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
Label::new("Use Zed AI")
|
||||||
.id("sign-in")
|
.size(LabelSize::Small)
|
||||||
.child(Label::new("Sign in to use Zed AI"))
|
.color(Color::Muted),
|
||||||
.cursor_pointer()
|
)
|
||||||
.on_click(cx.listener(|this, _event, cx| {
|
.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
|
let client = this
|
||||||
.workspace
|
.workspace
|
||||||
.update(cx, |workspace, _| workspace.client().clone())
|
.update(cx, |workspace, _| workspace.client().clone())
|
||||||
|
@ -2358,8 +2376,10 @@ impl ContextEditor {
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx)
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
})),
|
},
|
||||||
),
|
)),
|
||||||
|
))
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
} else if let Some(configuration_error) = configuration_error(cx) {
|
} else if let Some(configuration_error) = configuration_error(cx) {
|
||||||
let label = match configuration_error {
|
let label = match configuration_error {
|
||||||
|
@ -2369,27 +2389,51 @@ impl ContextEditor {
|
||||||
Some(
|
Some(
|
||||||
v_flex()
|
v_flex()
|
||||||
.elevation_3(cx)
|
.elevation_3(cx)
|
||||||
.p_4()
|
.p_2()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(Label::new(label))
|
.child(Label::new(label).size(LabelSize::Small).color(Color::Muted))
|
||||||
.child(
|
.child(
|
||||||
div()
|
h_flex().justify_end().child(
|
||||||
.id("open-configuration")
|
Button::new("open-configuration", "Open configuration")
|
||||||
.child(Label::new("Open configuration"))
|
.icon(IconName::Settings)
|
||||||
.cursor_pointer()
|
.icon_size(IconSize::Small)
|
||||||
.on_click({
|
.on_click({
|
||||||
let focus_handle = self.focus_handle(cx).clone();
|
let focus_handle = self.focus_handle(cx).clone();
|
||||||
move |_event, cx| {
|
move |_event, cx| {
|
||||||
focus_handle.dispatch_action(&ShowConfiguration, cx);
|
focus_handle.dispatch_action(&ShowConfiguration, cx);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_error_popover(error: SharedString, cx: &mut ViewContext<Self>) -> 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<Self>) -> impl IntoElement {
|
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let focus_handle = self.focus_handle(cx).clone();
|
let focus_handle = self.focus_handle(cx).clone();
|
||||||
let button_text = match self.workflow_step_for_cursor(cx) {
|
let button_text = match self.workflow_step_for_cursor(cx) {
|
||||||
|
@ -2487,23 +2531,32 @@ impl Render for ContextEditor {
|
||||||
div()
|
div()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.bg(cx.theme().colors().editor_background)
|
.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(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.absolute()
|
.absolute()
|
||||||
.bottom_0()
|
.right_4()
|
||||||
.p_4()
|
.bottom_2()
|
||||||
.justify_end()
|
.justify_end()
|
||||||
.child(
|
.child(self.render_send_button(cx)),
|
||||||
v_flex()
|
|
||||||
.gap_2()
|
|
||||||
.items_end()
|
|
||||||
.when_some(self.render_notice(cx), |this, notice| {
|
|
||||||
this.child(notice)
|
|
||||||
})
|
|
||||||
.child(self.render_send_button(cx)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,6 +281,7 @@ impl ContextOperation {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ContextEvent {
|
pub enum ContextEvent {
|
||||||
|
AssistError(String),
|
||||||
MessagesEdited,
|
MessagesEdited,
|
||||||
SummaryChanged,
|
SummaryChanged,
|
||||||
EditStepsChanged,
|
EditStepsChanged,
|
||||||
|
@ -1580,6 +1581,10 @@ impl Context {
|
||||||
.err()
|
.err()
|
||||||
.map(|error| error.to_string().trim().to_string());
|
.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| {
|
this.update_metadata(assistant_message_id, cx, |metadata| {
|
||||||
if let Some(error_message) = error_message.as_ref() {
|
if let Some(error_message) = error_message.as_ref() {
|
||||||
metadata.status =
|
metadata.status =
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue