Detailed changes
@@ -3259,44 +3259,33 @@ impl AcpThreadView {
}
};
- Some(
- div()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(content),
- )
+ Some(div().child(content))
}
fn render_any_thread_error(&self, error: SharedString, cx: &mut Context<'_, Self>) -> Callout {
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
Callout::new()
- .icon(icon)
+ .severity(Severity::Error)
.title("Error")
.description(error.clone())
- .secondary_action(self.create_copy_button(error.to_string()))
- .primary_action(self.dismiss_error_button(cx))
- .bg_color(self.error_callout_bg(cx))
+ .actions_slot(self.create_copy_button(error.to_string()))
+ .dismiss_action(self.dismiss_error_button(cx))
}
fn render_payment_required_error(&self, cx: &mut Context<Self>) -> Callout {
const ERROR_MESSAGE: &str =
"You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
Callout::new()
- .icon(icon)
+ .severity(Severity::Error)
.title("Free Usage Exceeded")
.description(ERROR_MESSAGE)
- .tertiary_action(self.upgrade_button(cx))
- .secondary_action(self.create_copy_button(ERROR_MESSAGE))
- .primary_action(self.dismiss_error_button(cx))
- .bg_color(self.error_callout_bg(cx))
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .child(self.upgrade_button(cx))
+ .child(self.create_copy_button(ERROR_MESSAGE)),
+ )
+ .dismiss_action(self.dismiss_error_button(cx))
}
fn render_model_request_limit_reached_error(
@@ -3311,18 +3300,17 @@ impl AcpThreadView {
}
};
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
Callout::new()
- .icon(icon)
+ .severity(Severity::Error)
.title("Model Prompt Limit Reached")
.description(error_message)
- .tertiary_action(self.upgrade_button(cx))
- .secondary_action(self.create_copy_button(error_message))
- .primary_action(self.dismiss_error_button(cx))
- .bg_color(self.error_callout_bg(cx))
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .child(self.upgrade_button(cx))
+ .child(self.create_copy_button(error_message)),
+ )
+ .dismiss_action(self.dismiss_error_button(cx))
}
fn render_tool_use_limit_reached_error(
@@ -3338,52 +3326,59 @@ impl AcpThreadView {
let focus_handle = self.focus_handle(cx);
- let icon = Icon::new(IconName::Info)
- .size(IconSize::Small)
- .color(Color::Info);
-
Some(
Callout::new()
- .icon(icon)
+ .icon(IconName::Info)
.title("Consecutive tool use limit reached.")
- .when(supports_burn_mode, |this| {
- this.secondary_action(
- Button::new("continue-burn-mode", "Continue with Burn Mode")
- .style(ButtonStyle::Filled)
- .style(ButtonStyle::Tinted(ui::TintColor::Accent))
- .layer(ElevationIndex::ModalSurface)
- .label_size(LabelSize::Small)
- .key_binding(
- KeyBinding::for_action_in(
- &ContinueWithBurnMode,
- &focus_handle,
- window,
- cx,
- )
- .map(|kb| kb.size(rems_from_px(10.))),
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .when(supports_burn_mode, |this| {
+ this.child(
+ Button::new("continue-burn-mode", "Continue with Burn Mode")
+ .style(ButtonStyle::Filled)
+ .style(ButtonStyle::Tinted(ui::TintColor::Accent))
+ .layer(ElevationIndex::ModalSurface)
+ .label_size(LabelSize::Small)
+ .key_binding(
+ KeyBinding::for_action_in(
+ &ContinueWithBurnMode,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(10.))),
+ )
+ .tooltip(Tooltip::text(
+ "Enable Burn Mode for unlimited tool use.",
+ ))
+ .on_click({
+ cx.listener(move |this, _, _window, cx| {
+ thread.update(cx, |thread, _cx| {
+ thread.set_completion_mode(CompletionMode::Burn);
+ });
+ this.resume_chat(cx);
+ })
+ }),
)
- .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
- .on_click({
- cx.listener(move |this, _, _window, cx| {
- thread.update(cx, |thread, _cx| {
- thread.set_completion_mode(CompletionMode::Burn);
- });
+ })
+ .child(
+ Button::new("continue-conversation", "Continue")
+ .layer(ElevationIndex::ModalSurface)
+ .label_size(LabelSize::Small)
+ .key_binding(
+ KeyBinding::for_action_in(
+ &ContinueThread,
+ &focus_handle,
+ window,
+ cx,
+ )
+ .map(|kb| kb.size(rems_from_px(10.))),
+ )
+ .on_click(cx.listener(|this, _, _window, cx| {
this.resume_chat(cx);
- })
- }),
- )
- })
- .primary_action(
- Button::new("continue-conversation", "Continue")
- .layer(ElevationIndex::ModalSurface)
- .label_size(LabelSize::Small)
- .key_binding(
- KeyBinding::for_action_in(&ContinueThread, &focus_handle, window, cx)
- .map(|kb| kb.size(rems_from_px(10.))),
- )
- .on_click(cx.listener(|this, _, _window, cx| {
- this.resume_chat(cx);
- })),
+ })),
+ ),
),
)
}
@@ -3424,10 +3419,6 @@ impl AcpThreadView {
}
}))
}
-
- fn error_callout_bg(&self, cx: &Context<Self>) -> Hsla {
- cx.theme().status().error.opacity(0.08)
- }
}
impl Focusable for AcpThreadView {
@@ -2597,7 +2597,7 @@ impl ActiveThread {
.id(("message-container", ix))
.py_1()
.px_2p5()
- .child(Banner::new().severity(ui::Severity::Warning).child(message))
+ .child(Banner::new().severity(Severity::Warning).child(message))
}
fn render_message_thinking_segment(
@@ -454,7 +454,7 @@ impl Render for AddLlmProviderModal {
this.section(
Section::new().child(
Banner::new()
- .severity(ui::Severity::Warning)
+ .severity(Severity::Warning)
.child(div().text_xs().child(error)),
),
)
@@ -48,9 +48,8 @@ use feature_flags::{self, FeatureFlagAppExt};
use fs::Fs;
use gpui::{
Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
- Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, Hsla,
- KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*,
- pulsating_between,
+ Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext,
+ Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
};
use language::LanguageRegistry;
use language_model::{
@@ -2712,20 +2711,22 @@ impl AgentPanel {
action_slot: Option<AnyElement>,
cx: &mut Context<Self>,
) -> impl IntoElement {
- h_flex()
- .mt_2()
- .pl_1p5()
- .pb_1()
- .w_full()
- .justify_between()
- .border_b_1()
- .border_color(cx.theme().colors().border_variant)
- .child(
- Label::new(label.into())
- .size(LabelSize::Small)
- .color(Color::Muted),
- )
- .children(action_slot)
+ div().pl_1().pr_1p5().child(
+ h_flex()
+ .mt_2()
+ .pl_1p5()
+ .pb_1()
+ .w_full()
+ .justify_between()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .child(
+ Label::new(label.into())
+ .size(LabelSize::Small)
+ .color(Color::Muted),
+ )
+ .children(action_slot),
+ )
}
fn render_thread_empty_state(
@@ -2831,22 +2832,12 @@ impl AgentPanel {
}),
),
)
- })
- .when_some(configuration_error.as_ref(), |this, err| {
- this.child(self.render_configuration_error(
- err,
- &focus_handle,
- window,
- cx,
- ))
}),
)
})
.when(!recent_history.is_empty(), |parent| {
- let focus_handle = focus_handle.clone();
parent
.overflow_hidden()
- .p_1p5()
.justify_end()
.gap_1()
.child(
@@ -2874,10 +2865,11 @@ impl AgentPanel {
),
)
.child(
- v_flex()
- .gap_1()
- .children(recent_history.into_iter().enumerate().map(
- |(index, entry)| {
+ v_flex().p_1().pr_1p5().gap_1().children(
+ recent_history
+ .into_iter()
+ .enumerate()
+ .map(|(index, entry)| {
// TODO: Add keyboard navigation.
let is_hovered =
self.hovered_recent_history_item == Some(index);
@@ -2896,30 +2888,68 @@ impl AgentPanel {
},
))
.into_any_element()
- },
- )),
+ }),
+ ),
)
- .when_some(configuration_error.as_ref(), |this, err| {
- this.child(self.render_configuration_error(err, &focus_handle, window, cx))
- })
+ })
+ .when_some(configuration_error.as_ref(), |this, err| {
+ this.child(self.render_configuration_error(false, err, &focus_handle, window, cx))
})
}
fn render_configuration_error(
&self,
+ border_bottom: bool,
configuration_error: &ConfigurationError,
focus_handle: &FocusHandle,
window: &mut Window,
cx: &mut App,
) -> impl IntoElement {
- match configuration_error {
- ConfigurationError::ModelNotFound
- | ConfigurationError::ProviderNotAuthenticated(_)
- | ConfigurationError::NoProvider => Banner::new()
- .severity(ui::Severity::Warning)
- .child(Label::new(configuration_error.to_string()))
- .action_slot(
- Button::new("settings", "Configure Provider")
+ let zed_provider_configured = AgentSettings::get_global(cx)
+ .default_model
+ .as_ref()
+ .map_or(false, |selection| {
+ selection.provider.0.as_str() == "zed.dev"
+ });
+
+ let callout = if zed_provider_configured {
+ Callout::new()
+ .icon(IconName::Warning)
+ .severity(Severity::Warning)
+ .when(border_bottom, |this| {
+ this.border_position(ui::BorderPosition::Bottom)
+ })
+ .title("Sign in to continue using Zed as your LLM provider.")
+ .actions_slot(
+ Button::new("sign_in", "Sign In")
+ .style(ButtonStyle::Tinted(ui::TintColor::Warning))
+ .label_size(LabelSize::Small)
+ .on_click({
+ let workspace = self.workspace.clone();
+ move |_, _, cx| {
+ let Ok(client) =
+ workspace.update(cx, |workspace, _| workspace.client().clone())
+ else {
+ return;
+ };
+
+ cx.spawn(async move |cx| {
+ client.sign_in_with_optional_connect(true, cx).await
+ })
+ .detach_and_log_err(cx);
+ }
+ }),
+ )
+ } else {
+ Callout::new()
+ .icon(IconName::Warning)
+ .severity(Severity::Warning)
+ .when(border_bottom, |this| {
+ this.border_position(ui::BorderPosition::Bottom)
+ })
+ .title(configuration_error.to_string())
+ .actions_slot(
+ Button::new("settings", "Configure")
.style(ButtonStyle::Tinted(ui::TintColor::Warning))
.label_size(LabelSize::Small)
.key_binding(
@@ -2929,16 +2959,23 @@ impl AgentPanel {
.on_click(|_event, window, cx| {
window.dispatch_action(OpenSettings.boxed_clone(), cx)
}),
- ),
+ )
+ };
+
+ match configuration_error {
+ ConfigurationError::ModelNotFound
+ | ConfigurationError::ProviderNotAuthenticated(_)
+ | ConfigurationError::NoProvider => callout.into_any_element(),
ConfigurationError::ProviderPendingTermsAcceptance(provider) => {
- Banner::new().severity(ui::Severity::Warning).child(
- h_flex().w_full().children(
+ Banner::new()
+ .severity(Severity::Warning)
+ .child(h_flex().w_full().children(
provider.render_accept_terms(
LanguageModelProviderTosView::ThreadEmptyState,
cx,
),
- ),
- )
+ ))
+ .into_any_element()
}
}
}
@@ -2970,7 +3007,7 @@ impl AgentPanel {
let focus_handle = self.focus_handle(cx);
let banner = Banner::new()
- .severity(ui::Severity::Info)
+ .severity(Severity::Info)
.child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
.action_slot(
h_flex()
@@ -3081,10 +3118,6 @@ impl AgentPanel {
}))
}
- fn error_callout_bg(&self, cx: &Context<Self>) -> Hsla {
- cx.theme().status().error.opacity(0.08)
- }
-
fn render_payment_required_error(
&self,
thread: &Entity<ActiveThread>,
@@ -3093,23 +3126,18 @@ impl AgentPanel {
const ERROR_MESSAGE: &str =
"You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
- div()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(
- Callout::new()
- .icon(icon)
- .title("Free Usage Exceeded")
- .description(ERROR_MESSAGE)
- .tertiary_action(self.upgrade_button(thread, cx))
- .secondary_action(self.create_copy_button(ERROR_MESSAGE))
- .primary_action(self.dismiss_error_button(thread, cx))
- .bg_color(self.error_callout_bg(cx)),
+ Callout::new()
+ .severity(Severity::Error)
+ .icon(IconName::XCircle)
+ .title("Free Usage Exceeded")
+ .description(ERROR_MESSAGE)
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .child(self.upgrade_button(thread, cx))
+ .child(self.create_copy_button(ERROR_MESSAGE)),
)
+ .dismiss_action(self.dismiss_error_button(thread, cx))
.into_any_element()
}
@@ -3124,40 +3152,22 @@ impl AgentPanel {
Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
};
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
- div()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(
- Callout::new()
- .icon(icon)
- .title("Model Prompt Limit Reached")
- .description(error_message)
- .tertiary_action(self.upgrade_button(thread, cx))
- .secondary_action(self.create_copy_button(error_message))
- .primary_action(self.dismiss_error_button(thread, cx))
- .bg_color(self.error_callout_bg(cx)),
+ Callout::new()
+ .severity(Severity::Error)
+ .title("Model Prompt Limit Reached")
+ .description(error_message)
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .child(self.upgrade_button(thread, cx))
+ .child(self.create_copy_button(error_message)),
)
+ .dismiss_action(self.dismiss_error_button(thread, cx))
.into_any_element()
}
- fn render_error_message(
- &self,
- header: SharedString,
- message: SharedString,
- thread: &Entity<ActiveThread>,
- cx: &mut Context<Self>,
- ) -> AnyElement {
- let message_with_header = format!("{}\n{}", header, message);
-
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
- let retry_button = Button::new("retry", "Retry")
+ fn render_retry_button(&self, thread: &Entity<ActiveThread>) -> AnyElement {
+ Button::new("retry", "Retry")
.icon(IconName::RotateCw)
.icon_position(IconPosition::Start)
.icon_size(IconSize::Small)
@@ -3172,21 +3182,31 @@ impl AgentPanel {
});
});
}
- });
+ })
+ .into_any_element()
+ }
- div()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(
- Callout::new()
- .icon(icon)
- .title(header)
- .description(message.clone())
- .primary_action(retry_button)
- .secondary_action(self.dismiss_error_button(thread, cx))
- .tertiary_action(self.create_copy_button(message_with_header))
- .bg_color(self.error_callout_bg(cx)),
+ fn render_error_message(
+ &self,
+ header: SharedString,
+ message: SharedString,
+ thread: &Entity<ActiveThread>,
+ cx: &mut Context<Self>,
+ ) -> AnyElement {
+ let message_with_header = format!("{}\n{}", header, message);
+
+ Callout::new()
+ .severity(Severity::Error)
+ .icon(IconName::XCircle)
+ .title(header)
+ .description(message.clone())
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .child(self.render_retry_button(thread))
+ .child(self.create_copy_button(message_with_header)),
)
+ .dismiss_action(self.dismiss_error_button(thread, cx))
.into_any_element()
}
@@ -3195,60 +3215,39 @@ impl AgentPanel {
message: SharedString,
can_enable_burn_mode: bool,
thread: &Entity<ActiveThread>,
- cx: &mut Context<Self>,
) -> AnyElement {
- let icon = Icon::new(IconName::XCircle)
- .size(IconSize::Small)
- .color(Color::Error);
-
- let retry_button = Button::new("retry", "Retry")
- .icon(IconName::RotateCw)
- .icon_position(IconPosition::Start)
- .icon_size(IconSize::Small)
- .label_size(LabelSize::Small)
- .on_click({
- let thread = thread.clone();
- move |_, window, cx| {
- thread.update(cx, |thread, cx| {
- thread.clear_last_error();
- thread.thread().update(cx, |thread, cx| {
- thread.retry_last_completion(Some(window.window_handle()), cx);
- });
- });
- }
- });
-
- let mut callout = Callout::new()
- .icon(icon)
+ Callout::new()
+ .severity(Severity::Error)
.title("Error")
.description(message.clone())
- .bg_color(self.error_callout_bg(cx))
- .primary_action(retry_button);
-
- if can_enable_burn_mode {
- let burn_mode_button = Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
- .icon(IconName::ZedBurnMode)
- .icon_position(IconPosition::Start)
- .icon_size(IconSize::Small)
- .label_size(LabelSize::Small)
- .on_click({
- let thread = thread.clone();
- move |_, window, cx| {
- thread.update(cx, |thread, cx| {
- thread.clear_last_error();
- thread.thread().update(cx, |thread, cx| {
- thread.enable_burn_mode_and_retry(Some(window.window_handle()), cx);
- });
- });
- }
- });
- callout = callout.secondary_action(burn_mode_button);
- }
-
- div()
- .border_t_1()
- .border_color(cx.theme().colors().border)
- .child(callout)
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .when(can_enable_burn_mode, |this| {
+ this.child(
+ Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
+ .icon(IconName::ZedBurnMode)
+ .icon_position(IconPosition::Start)
+ .icon_size(IconSize::Small)
+ .label_size(LabelSize::Small)
+ .on_click({
+ let thread = thread.clone();
+ move |_, window, cx| {
+ thread.update(cx, |thread, cx| {
+ thread.clear_last_error();
+ thread.thread().update(cx, |thread, cx| {
+ thread.enable_burn_mode_and_retry(
+ Some(window.window_handle()),
+ cx,
+ );
+ });
+ });
+ }
+ }),
+ )
+ })
+ .child(self.render_retry_button(thread)),
+ )
.into_any_element()
}
@@ -3503,7 +3502,6 @@ impl Render for AgentPanel {
message,
can_enable_burn_mode,
thread,
- cx,
),
})
.into_any(),
@@ -3531,16 +3529,13 @@ impl Render for AgentPanel {
if !self.should_render_onboarding(cx)
&& let Some(err) = configuration_error.as_ref()
{
- this.child(
- div().bg(cx.theme().colors().editor_background).p_2().child(
- self.render_configuration_error(
- err,
- &self.focus_handle(cx),
- window,
- cx,
- ),
- ),
- )
+ this.child(self.render_configuration_error(
+ true,
+ err,
+ &self.focus_handle(cx),
+ window,
+ cx,
+ ))
} else {
this
}
@@ -1323,14 +1323,10 @@ impl MessageEditor {
token_usage_ratio: TokenUsageRatio,
cx: &mut Context<Self>,
) -> Option<Div> {
- let icon = if token_usage_ratio == TokenUsageRatio::Exceeded {
- Icon::new(IconName::Close)
- .color(Color::Error)
- .size(IconSize::XSmall)
+ let (icon, severity) = if token_usage_ratio == TokenUsageRatio::Exceeded {
+ (IconName::Close, Severity::Error)
} else {
- Icon::new(IconName::Warning)
- .color(Color::Warning)
- .size(IconSize::XSmall)
+ (IconName::Warning, Severity::Warning)
};
let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
@@ -1345,29 +1341,33 @@ impl MessageEditor {
"To continue, start a new thread from a summary."
};
- let mut callout = Callout::new()
+ let callout = Callout::new()
.line_height(line_height)
+ .severity(severity)
.icon(icon)
.title(title)
.description(description)
- .primary_action(
- Button::new("start-new-thread", "Start New Thread")
- .label_size(LabelSize::Small)
- .on_click(cx.listener(|this, _, window, cx| {
- let from_thread_id = Some(this.thread.read(cx).id().clone());
- window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
- })),
- );
-
- if self.is_using_zed_provider(cx) {
- callout = callout.secondary_action(
- IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
- .icon_size(IconSize::XSmall)
- .on_click(cx.listener(|this, _event, window, cx| {
- this.toggle_burn_mode(&ToggleBurnMode, window, cx);
- })),
+ .actions_slot(
+ h_flex()
+ .gap_0p5()
+ .when(self.is_using_zed_provider(cx), |this| {
+ this.child(
+ IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
+ .icon_size(IconSize::XSmall)
+ .on_click(cx.listener(|this, _event, window, cx| {
+ this.toggle_burn_mode(&ToggleBurnMode, window, cx);
+ })),
+ )
+ })
+ .child(
+ Button::new("start-new-thread", "Start New Thread")
+ .label_size(LabelSize::Small)
+ .on_click(cx.listener(|this, _, window, cx| {
+ let from_thread_id = Some(this.thread.read(cx).id().clone());
+ window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
+ })),
+ ),
);
- }
Some(
div()
@@ -80,14 +80,10 @@ impl RenderOnce for UsageCallout {
}
};
- let icon = if is_limit_reached {
- Icon::new(IconName::Close)
- .color(Color::Error)
- .size(IconSize::XSmall)
+ let (icon, severity) = if is_limit_reached {
+ (IconName::Close, Severity::Error)
} else {
- Icon::new(IconName::Warning)
- .color(Color::Warning)
- .size(IconSize::XSmall)
+ (IconName::Warning, Severity::Warning)
};
div()
@@ -95,10 +91,12 @@ impl RenderOnce for UsageCallout {
.border_color(cx.theme().colors().border)
.child(
Callout::new()
+ .icon(icon)
+ .severity(severity)
.icon(icon)
.title(title)
.description(message)
- .primary_action(
+ .actions_slot(
Button::new("upgrade", button_text)
.label_size(LabelSize::Small)
.on_click(move |_, _, cx| {
@@ -17,6 +17,6 @@ impl RenderOnce for YoungAccountBanner {
div()
.max_w_full()
.my_1()
- .child(Banner::new().severity(ui::Severity::Warning).child(label))
+ .child(Banner::new().severity(Severity::Warning).child(label))
}
}
@@ -21,7 +21,7 @@ impl Global for GlobalLanguageModelRegistry {}
pub enum ConfigurationError {
#[error("Configure at least one LLM provider to start using the panel.")]
NoProvider,
- #[error("LLM Provider is not configured or does not support the configured model.")]
+ #[error("LLM provider is not configured or does not support the configured model.")]
ModelNotFound,
#[error("{} LLM provider is not configured.", .0.name().0)]
ProviderNotAuthenticated(Arc<dyn LanguageModelProvider>),
@@ -2021,21 +2021,21 @@ impl RenderOnce for SyntaxHighlightedText {
#[derive(PartialEq)]
struct InputError {
- severity: ui::Severity,
+ severity: Severity,
content: SharedString,
}
impl InputError {
fn warning(message: impl Into<SharedString>) -> Self {
Self {
- severity: ui::Severity::Warning,
+ severity: Severity::Warning,
content: message.into(),
}
}
fn error(message: anyhow::Error) -> Self {
Self {
- severity: ui::Severity::Error,
+ severity: Severity::Error,
content: message.to_string().into(),
}
}
@@ -2162,9 +2162,11 @@ impl KeybindingEditorModal {
}
fn set_error(&mut self, error: InputError, cx: &mut Context<Self>) -> bool {
- if self.error.as_ref().is_some_and(|old_error| {
- old_error.severity == ui::Severity::Warning && *old_error == error
- }) {
+ if self
+ .error
+ .as_ref()
+ .is_some_and(|old_error| old_error.severity == Severity::Warning && *old_error == error)
+ {
false
} else {
self.error = Some(error);
@@ -1,15 +1,6 @@
use crate::prelude::*;
use gpui::{AnyElement, IntoElement, ParentElement, Styled};
-/// Severity levels that determine the style of the banner.
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub enum Severity {
- Info,
- Success,
- Warning,
- Error,
-}
-
/// Banners provide informative and brief messages without interrupting the user.
/// This component offers four severity levels that can be used depending on the message.
///
@@ -1,7 +1,13 @@
-use gpui::{AnyElement, Hsla};
+use gpui::AnyElement;
use crate::prelude::*;
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum BorderPosition {
+ Top,
+ Bottom,
+}
+
/// A callout component for displaying important information that requires user attention.
///
/// # Usage Example
@@ -10,42 +16,48 @@ use crate::prelude::*;
/// use ui::{Callout};
///
/// Callout::new()
-/// .icon(Icon::new(IconName::Warning).color(Color::Warning))
+/// .severity(Severity::Warning)
+/// .icon(IconName::Warning)
/// .title(Label::new("Be aware of your subscription!"))
/// .description(Label::new("Your subscription is about to expire. Renew now!"))
-/// .primary_action(Button::new("renew", "Renew Now"))
-/// .secondary_action(Button::new("remind", "Remind Me Later"))
+/// .actions_slot(Button::new("renew", "Renew Now"))
/// ```
///
#[derive(IntoElement, RegisterComponent)]
pub struct Callout {
- icon: Option<Icon>,
+ severity: Severity,
+ icon: Option<IconName>,
title: Option<SharedString>,
description: Option<SharedString>,
- primary_action: Option<AnyElement>,
- secondary_action: Option<AnyElement>,
- tertiary_action: Option<AnyElement>,
+ actions_slot: Option<AnyElement>,
+ dismiss_action: Option<AnyElement>,
line_height: Option<Pixels>,
- bg_color: Option<Hsla>,
+ border_position: BorderPosition,
}
impl Callout {
/// Creates a new `Callout` component with default styling.
pub fn new() -> Self {
Self {
+ severity: Severity::Info,
icon: None,
title: None,
description: None,
- primary_action: None,
- secondary_action: None,
- tertiary_action: None,
+ actions_slot: None,
+ dismiss_action: None,
line_height: None,
- bg_color: None,
+ border_position: BorderPosition::Top,
}
}
+ /// Sets the severity of the callout.
+ pub fn severity(mut self, severity: Severity) -> Self {
+ self.severity = severity;
+ self
+ }
+
/// Sets the icon to display in the callout.
- pub fn icon(mut self, icon: Icon) -> Self {
+ pub fn icon(mut self, icon: IconName) -> Self {
self.icon = Some(icon);
self
}
@@ -64,20 +76,14 @@ impl Callout {
}
/// Sets the primary call-to-action button.
- pub fn primary_action(mut self, action: impl IntoElement) -> Self {
- self.primary_action = Some(action.into_any_element());
- self
- }
-
- /// Sets an optional secondary call-to-action button.
- pub fn secondary_action(mut self, action: impl IntoElement) -> Self {
- self.secondary_action = Some(action.into_any_element());
+ pub fn actions_slot(mut self, action: impl IntoElement) -> Self {
+ self.actions_slot = Some(action.into_any_element());
self
}
/// Sets an optional tertiary call-to-action button.
- pub fn tertiary_action(mut self, action: impl IntoElement) -> Self {
- self.tertiary_action = Some(action.into_any_element());
+ pub fn dismiss_action(mut self, action: impl IntoElement) -> Self {
+ self.dismiss_action = Some(action.into_any_element());
self
}
@@ -87,9 +93,9 @@ impl Callout {
self
}
- /// Sets a custom background color for the callout content.
- pub fn bg_color(mut self, color: Hsla) -> Self {
- self.bg_color = Some(color);
+ /// Sets the border position in the callout.
+ pub fn border_position(mut self, border_position: BorderPosition) -> Self {
+ self.border_position = border_position;
self
}
}
@@ -97,21 +103,51 @@ impl Callout {
impl RenderOnce for Callout {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let line_height = self.line_height.unwrap_or(window.line_height());
- let bg_color = self
- .bg_color
- .unwrap_or(cx.theme().colors().panel_background);
- let has_actions = self.primary_action.is_some()
- || self.secondary_action.is_some()
- || self.tertiary_action.is_some();
+
+ let has_actions = self.actions_slot.is_some() || self.dismiss_action.is_some();
+
+ let (icon, icon_color, bg_color) = match self.severity {
+ Severity::Info => (
+ IconName::Info,
+ Color::Muted,
+ cx.theme().colors().panel_background.opacity(0.),
+ ),
+ Severity::Success => (
+ IconName::Check,
+ Color::Success,
+ cx.theme().status().success.opacity(0.1),
+ ),
+ Severity::Warning => (
+ IconName::Warning,
+ Color::Warning,
+ cx.theme().status().warning_background.opacity(0.2),
+ ),
+ Severity::Error => (
+ IconName::XCircle,
+ Color::Error,
+ cx.theme().status().error.opacity(0.08),
+ ),
+ };
h_flex()
+ .min_w_0()
.p_2()
.gap_2()
.items_start()
+ .map(|this| match self.border_position {
+ BorderPosition::Top => this.border_t_1(),
+ BorderPosition::Bottom => this.border_b_1(),
+ })
+ .border_color(cx.theme().colors().border)
.bg(bg_color)
.overflow_x_hidden()
- .when_some(self.icon, |this, icon| {
- this.child(h_flex().h(line_height).justify_center().child(icon))
+ .when(self.icon.is_some(), |this| {
+ this.child(
+ h_flex()
+ .h(line_height)
+ .justify_center()
+ .child(Icon::new(icon).size(IconSize::Small).color(icon_color)),
+ )
})
.child(
v_flex()
@@ -119,10 +155,11 @@ impl RenderOnce for Callout {
.w_full()
.child(
h_flex()
- .h(line_height)
+ .min_h(line_height)
.w_full()
.gap_1()
.justify_between()
+ .flex_wrap()
.when_some(self.title, |this, title| {
this.child(h_flex().child(Label::new(title).size(LabelSize::Small)))
})
@@ -130,13 +167,10 @@ impl RenderOnce for Callout {
this.child(
h_flex()
.gap_0p5()
- .when_some(self.tertiary_action, |this, action| {
+ .when_some(self.actions_slot, |this, action| {
this.child(action)
})
- .when_some(self.secondary_action, |this, action| {
- this.child(action)
- })
- .when_some(self.primary_action, |this, action| {
+ .when_some(self.dismiss_action, |this, action| {
this.child(action)
}),
)
@@ -168,84 +202,101 @@ impl Component for Callout {
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
- let callout_examples = vec![
+ let single_action = || Button::new("got-it", "Got it").label_size(LabelSize::Small);
+ let multiple_actions = || {
+ h_flex()
+ .gap_0p5()
+ .child(Button::new("update", "Backup & Update").label_size(LabelSize::Small))
+ .child(Button::new("dismiss", "Dismiss").label_size(LabelSize::Small))
+ };
+
+ let basic_examples = vec![
single_example(
"Simple with Title Only",
Callout::new()
- .icon(
- Icon::new(IconName::Info)
- .color(Color::Accent)
- .size(IconSize::Small),
- )
+ .icon(IconName::Info)
.title("System maintenance scheduled for tonight")
- .primary_action(Button::new("got-it", "Got it").label_size(LabelSize::Small))
+ .actions_slot(single_action())
.into_any_element(),
)
.width(px(580.)),
single_example(
"With Title and Description",
Callout::new()
- .icon(
- Icon::new(IconName::Warning)
- .color(Color::Warning)
- .size(IconSize::Small),
- )
+ .icon(IconName::Warning)
.title("Your settings contain deprecated values")
.description(
"We'll backup your current settings and update them to the new format.",
)
- .primary_action(
- Button::new("update", "Backup & Update").label_size(LabelSize::Small),
- )
- .secondary_action(
- Button::new("dismiss", "Dismiss").label_size(LabelSize::Small),
- )
+ .actions_slot(single_action())
.into_any_element(),
)
.width(px(580.)),
single_example(
"Error with Multiple Actions",
Callout::new()
- .icon(
- Icon::new(IconName::Close)
- .color(Color::Error)
- .size(IconSize::Small),
- )
+ .icon(IconName::Close)
.title("Thread reached the token limit")
.description("Start a new thread from a summary to continue the conversation.")
- .primary_action(
- Button::new("new-thread", "Start New Thread").label_size(LabelSize::Small),
- )
- .secondary_action(
- Button::new("view-summary", "View Summary").label_size(LabelSize::Small),
- )
+ .actions_slot(multiple_actions())
.into_any_element(),
)
.width(px(580.)),
single_example(
"Multi-line Description",
Callout::new()
- .icon(
- Icon::new(IconName::Sparkle)
- .color(Color::Accent)
- .size(IconSize::Small),
- )
+ .icon(IconName::Sparkle)
.title("Upgrade to Pro")
.description("⢠Unlimited threads\n⢠Priority support\n⢠Advanced analytics")
- .primary_action(
- Button::new("upgrade", "Upgrade Now").label_size(LabelSize::Small),
- )
- .secondary_action(
- Button::new("learn-more", "Learn More").label_size(LabelSize::Small),
- )
+ .actions_slot(multiple_actions())
.into_any_element(),
)
.width(px(580.)),
];
+ let severity_examples = vec![
+ single_example(
+ "Info",
+ Callout::new()
+ .icon(IconName::Info)
+ .title("System maintenance scheduled for tonight")
+ .actions_slot(single_action())
+ .into_any_element(),
+ ),
+ single_example(
+ "Warning",
+ Callout::new()
+ .severity(Severity::Warning)
+ .icon(IconName::Triangle)
+ .title("System maintenance scheduled for tonight")
+ .actions_slot(single_action())
+ .into_any_element(),
+ ),
+ single_example(
+ "Error",
+ Callout::new()
+ .severity(Severity::Error)
+ .icon(IconName::XCircle)
+ .title("System maintenance scheduled for tonight")
+ .actions_slot(single_action())
+ .into_any_element(),
+ ),
+ single_example(
+ "Success",
+ Callout::new()
+ .severity(Severity::Success)
+ .icon(IconName::Check)
+ .title("System maintenance scheduled for tonight")
+ .actions_slot(single_action())
+ .into_any_element(),
+ ),
+ ];
+
Some(
- example_group(callout_examples)
- .vertical()
+ v_flex()
+ .gap_4()
+ .child(example_group(basic_examples).vertical())
+ .child(example_group_with_title("Severity", severity_examples).vertical())
.into_any_element(),
)
}
@@ -14,7 +14,9 @@ pub use ui_macros::RegisterComponent;
pub use crate::DynamicSpacing;
pub use crate::animation::{AnimationDirection, AnimationDuration, DefaultAnimations};
-pub use crate::styles::{PlatformStyle, StyledTypography, TextSize, rems_from_px, vh, vw};
+pub use crate::styles::{
+ PlatformStyle, Severity, StyledTypography, TextSize, rems_from_px, vh, vw,
+};
pub use crate::traits::clickable::*;
pub use crate::traits::disableable::*;
pub use crate::traits::fixed::*;
@@ -3,6 +3,7 @@ mod appearance;
mod color;
mod elevation;
mod platform;
+mod severity;
mod spacing;
mod typography;
mod units;
@@ -11,6 +12,7 @@ pub use appearance::*;
pub use color::*;
pub use elevation::*;
pub use platform::*;
+pub use severity::*;
pub use spacing::*;
pub use typography::*;
pub use units::*;
@@ -0,0 +1,10 @@
+/// Severity levels that determine the style of the component.
+/// Usually, it affects the background. Most of the time,
+/// it also follows with an icon corresponding the severity level.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Severity {
+ Info,
+ Success,
+ Warning,
+ Error,
+}