agent: Improve error and warnings display (#36425)
This PR refactors the callout component and improves how we display errors and warnings in the agent panel, along with improvements for specific cases (e.g., you have `zed.dev` as your LLM provider and is signed out). Still a work in progress, though, wrapping up some details. Release Notes: - N/A
This commit is contained in:
parent
b578031120
commit
b7edc89a87
14 changed files with 436 additions and 394 deletions
|
@ -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,23 +3152,37 @@ 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_retry_button(&self, thread: &Entity<ActiveThread>) -> AnyElement {
|
||||
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);
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
|
@ -3153,40 +3195,18 @@ impl AgentPanel {
|
|||
) -> 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")
|
||||
.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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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)),
|
||||
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,30 +1341,34 @@ 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);
|
||||
})),
|
||||
.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);
|
||||
})),
|
||||
),
|
||||
);
|
||||
|
||||
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);
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
Some(
|
||||
div()
|
||||
.border_t_1()
|
||||
|
|
|
@ -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::*;
|
||||
|
|
10
crates/ui/src/styles/severity.rs
Normal file
10
crates/ui/src/styles/severity.rs
Normal file
|
@ -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,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue