agent_ui: Make thread loading state clearer (#43765)
Closes https://github.com/zed-industries/zed/issues/43721 This PR makes the loading state clearer by disabling the message editor while the agent is loading as well as pulsating the icon. Release Notes: - agent: Made the thread loading state clearer in the agent panel.
This commit is contained in:
@@ -675,6 +675,8 @@ impl AcpThreadView {
|
||||
})
|
||||
});
|
||||
|
||||
this.message_editor.focus_handle(cx).focus(window);
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
Err(err) => {
|
||||
@@ -1009,6 +1011,10 @@ impl AcpThreadView {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_loading(&self) -> bool {
|
||||
matches!(self.thread_state, ThreadState::Loading { .. })
|
||||
}
|
||||
|
||||
fn resume_chat(&mut self, cx: &mut Context<Self>) {
|
||||
self.thread_error.take();
|
||||
let Some(thread) = self.thread() else {
|
||||
@@ -4196,8 +4202,10 @@ impl AcpThreadView {
|
||||
.block_mouse_except_scroll();
|
||||
|
||||
let enable_editor = match self.thread_state {
|
||||
ThreadState::Loading { .. } | ThreadState::Ready { .. } => true,
|
||||
ThreadState::Unauthenticated { .. } | ThreadState::LoadError(..) => false,
|
||||
ThreadState::Ready { .. } => true,
|
||||
ThreadState::Loading { .. }
|
||||
| ThreadState::Unauthenticated { .. }
|
||||
| ThreadState::LoadError(..) => false,
|
||||
};
|
||||
|
||||
v_flex()
|
||||
@@ -5858,12 +5866,10 @@ fn placeholder_text(agent_name: &str, has_commands: bool) -> String {
|
||||
impl Focusable for AcpThreadView {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
match self.thread_state {
|
||||
ThreadState::Loading { .. } | ThreadState::Ready { .. } => {
|
||||
self.active_editor(cx).focus_handle(cx)
|
||||
}
|
||||
ThreadState::LoadError(_) | ThreadState::Unauthenticated { .. } => {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
ThreadState::Ready { .. } => self.active_editor(cx).focus_handle(cx),
|
||||
ThreadState::Loading { .. }
|
||||
| ThreadState::LoadError(_)
|
||||
| ThreadState::Unauthenticated { .. } => self.focus_handle.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use std::ops::Range;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Range, path::Path, rc::Rc, sync::Arc, time::Duration};
|
||||
|
||||
use acp_thread::AcpThread;
|
||||
use agent::{ContextServerRegistry, DbThreadMetadata, HistoryEntry, HistoryStore};
|
||||
@@ -46,9 +43,9 @@ use extension::ExtensionEvents;
|
||||
use extension_host::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use gpui::{
|
||||
Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
|
||||
ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
|
||||
WeakEntity, prelude::*,
|
||||
Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext, Corner, DismissEvent,
|
||||
Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription,
|
||||
Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use language_model::{ConfigurationError, LanguageModelRegistry};
|
||||
@@ -58,10 +55,9 @@ use rules_library::{RulesLibrary, open_rules_library};
|
||||
use search::{BufferSearchBar, buffer_search};
|
||||
use settings::{Settings, update_settings_file};
|
||||
use theme::ThemeSettings;
|
||||
use ui::utils::WithRemSize;
|
||||
use ui::{
|
||||
Callout, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
ProgressBar, Tab, Tooltip, prelude::*,
|
||||
ProgressBar, Tab, Tooltip, prelude::*, utils::WithRemSize,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use workspace::{
|
||||
@@ -2159,28 +2155,41 @@ impl AgentPanel {
|
||||
|
||||
let selected_agent_label = self.selected_agent.label();
|
||||
|
||||
let is_thread_loading = self
|
||||
.active_thread_view()
|
||||
.map(|thread| thread.read(cx).is_loading())
|
||||
.unwrap_or(false);
|
||||
|
||||
let has_custom_icon = selected_agent_custom_icon.is_some();
|
||||
|
||||
let selected_agent = div()
|
||||
.id("selected_agent_icon")
|
||||
.when_some(selected_agent_custom_icon, |this, icon_path| {
|
||||
let label = selected_agent_label.clone();
|
||||
this.px_1()
|
||||
.child(Icon::from_external_svg(icon_path).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
})
|
||||
.when(!has_custom_icon, |this| {
|
||||
this.when_some(self.selected_agent.icon(), |this, icon| {
|
||||
let label = selected_agent_label.clone();
|
||||
this.px_1()
|
||||
.child(Icon::new(icon).color(Color::Muted))
|
||||
.tooltip(move |_window, cx| {
|
||||
Tooltip::with_meta(label.clone(), None, "Selected Agent", cx)
|
||||
})
|
||||
this.px_1().child(Icon::new(icon).color(Color::Muted))
|
||||
})
|
||||
})
|
||||
.into_any_element();
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::with_meta(selected_agent_label.clone(), None, "Selected Agent", cx)
|
||||
});
|
||||
|
||||
let selected_agent = if is_thread_loading {
|
||||
selected_agent
|
||||
.with_animation(
|
||||
"pulsating-icon",
|
||||
Animation::new(Duration::from_secs(1))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(0.2, 0.6)),
|
||||
|icon, delta| icon.opacity(delta),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
selected_agent.into_any_element()
|
||||
};
|
||||
|
||||
h_flex()
|
||||
.id("agent-panel-toolbar")
|
||||
|
||||
Reference in New Issue
Block a user