diff --git a/assets/icons/sparkle.svg b/assets/icons/sparkle.svg
new file mode 100644
index 0000000000..f420f527f1
--- /dev/null
+++ b/assets/icons/sparkle.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/sparkle_filled.svg b/assets/icons/sparkle_filled.svg
new file mode 100644
index 0000000000..96837f618d
--- /dev/null
+++ b/assets/icons/sparkle_filled.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs
index 0c96d0edb2..e8322a9285 100644
--- a/crates/assistant/src/prompt_library.rs
+++ b/crates/assistant/src/prompt_library.rs
@@ -13,9 +13,10 @@ use futures::{
};
use fuzzy::StringMatchCandidate;
use gpui::{
- actions, point, size, AnyElement, AppContext, BackgroundExecutor, Bounds, DevicePixels,
- EventEmitter, Global, PromptLevel, ReadGlobal, Subscription, Task, TitlebarOptions,
- UpdateGlobal, View, WindowBounds, WindowHandle, WindowOptions,
+ actions, percentage, point, size, Animation, AnimationExt, AnyElement, AppContext,
+ BackgroundExecutor, Bounds, DevicePixels, EventEmitter, Global, PromptLevel, ReadGlobal,
+ Subscription, Task, TitlebarOptions, Transformation, UpdateGlobal, View, WindowBounds,
+ WindowHandle, WindowOptions,
};
use heed::{types::SerdeBincode, Database, RoTxn};
use language::{language_settings::SoftWrap, Buffer, LanguageRegistry};
@@ -251,7 +252,11 @@ impl PickerDelegate for PromptPickerDelegate {
let element = match prompt {
PromptPickerEntry::DefaultPromptsHeader => ListHeader::new("Default Prompts")
.inset(true)
- .start_slot(Icon::new(IconName::ZedAssistant))
+ .start_slot(
+ Icon::new(IconName::Sparkle)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
.selected(selected)
.into_any_element(),
PromptPickerEntry::DefaultPromptsEmpty => {
@@ -262,7 +267,11 @@ impl PickerDelegate for PromptPickerDelegate {
}
PromptPickerEntry::AllPromptsHeader => ListHeader::new("All Prompts")
.inset(true)
- .start_slot(Icon::new(IconName::Library))
+ .start_slot(
+ Icon::new(IconName::Library)
+ .color(Color::Muted)
+ .size(IconSize::XSmall),
+ )
.selected(selected)
.into_any_element(),
PromptPickerEntry::AllPromptsEmpty => ListSubHeader::new("No prompts")
@@ -276,14 +285,15 @@ impl PickerDelegate for PromptPickerDelegate {
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
- .child(Label::new(
+ .child(h_flex().h_5().line_height(relative(1.)).child(Label::new(
prompt.title.clone().unwrap_or("Untitled".into()),
- ))
+ )))
.end_hover_slot(
h_flex()
.gap_2()
.child(
IconButton::new("delete-prompt", IconName::Trash)
+ .icon_color(Color::Muted)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::text("Delete Prompt", cx))
.on_click(cx.listener(move |_, _, cx| {
@@ -291,30 +301,24 @@ impl PickerDelegate for PromptPickerDelegate {
})),
)
.child(
- IconButton::new(
- "toggle-default-prompt",
- if default {
- IconName::ZedAssistantFilled
- } else {
- IconName::ZedAssistant
- },
- )
- .shape(IconButtonShape::Square)
- .tooltip(move |cx| {
- Tooltip::text(
- if default {
- "Remove from Default Prompt"
- } else {
- "Add to Default Prompt"
- },
- cx,
- )
- })
- .on_click(cx.listener(
- move |_, _, cx| {
+ IconButton::new("toggle-default-prompt", IconName::Sparkle)
+ .selected(default)
+ .selected_icon(IconName::SparkleFilled)
+ .icon_color(if default { Color::Accent } else { Color::Muted })
+ .shape(IconButtonShape::Square)
+ .tooltip(move |cx| {
+ Tooltip::text(
+ if default {
+ "Remove from Default Prompt"
+ } else {
+ "Add to Default Prompt"
+ },
+ cx,
+ )
+ })
+ .on_click(cx.listener(move |_, _, cx| {
cx.emit(PromptPickerEvent::ToggledDefault { prompt_id })
- },
- )),
+ })),
),
)
.into_any_element()
@@ -322,6 +326,18 @@ impl PickerDelegate for PromptPickerDelegate {
};
Some(element)
}
+
+ fn render_editor(&self, editor: &View, cx: &mut ViewContext>) -> Div {
+ h_flex()
+ .bg(cx.theme().colors().editor_background)
+ .rounded_md()
+ .overflow_hidden()
+ .flex_none()
+ .py_1()
+ .px_2()
+ .mx_2()
+ .child(editor.clone())
+ }
}
impl PromptLibrary {
@@ -748,14 +764,13 @@ impl PromptLibrary {
.child(
h_flex()
.p(Spacing::Small.rems(cx))
- .border_b_1()
- .border_color(cx.theme().colors().border)
.h(TitleBar::height(cx))
.w_full()
.flex_none()
.justify_end()
.child(
IconButton::new("new-prompt", IconName::Plus)
+ .style(ButtonStyle::Transparent)
.shape(IconButtonShape::Square)
.tooltip(move |cx| Tooltip::for_action("New Prompt", &NewPrompt, cx))
.on_click(|_, cx| {
@@ -777,12 +792,21 @@ impl PromptLibrary {
.flex_none()
.min_w_64()
.children(self.active_prompt_id.and_then(|prompt_id| {
+ let buffer_font = ThemeSettings::get_global(cx).buffer_font.family.clone();
let prompt_metadata = self.store.metadata(prompt_id)?;
let prompt_editor = &self.prompt_editors[&prompt_id];
+ let focus_handle = prompt_editor.editor.focus_handle(cx);
+ let current_model = CompletionProvider::global(cx).model();
+ let token_count = prompt_editor.token_count.map(|count| count.to_string());
+
Some(
h_flex()
+ .id("prompt-editor-inner")
.size_full()
.items_start()
+ .on_click(cx.listener(move |_, _, cx| {
+ cx.focus(&focus_handle);
+ }))
.child(
div()
.on_action(cx.listener(Self::focus_picker))
@@ -790,8 +814,8 @@ impl PromptLibrary {
.on_action(cx.listener(Self::cancel_last_inline_assist))
.flex_grow()
.h_full()
- .pt(Spacing::Large.rems(cx))
- .pl(Spacing::Large.rems(cx))
+ .pt(Spacing::XXLarge.rems(cx))
+ .pl(Spacing::XXLarge.rems(cx))
.child(prompt_editor.editor.clone()),
)
.child(
@@ -799,49 +823,92 @@ impl PromptLibrary {
.w_12()
.py(Spacing::Large.rems(cx))
.justify_start()
- .items_center()
- .gap_4()
- .child(
- IconButton::new(
- "toggle-default-prompt",
- if prompt_metadata.default {
- IconName::ZedAssistantFilled
- } else {
- IconName::ZedAssistant
- },
- )
- .size(ButtonSize::Large)
- .shape(IconButtonShape::Square)
- .tooltip(move |cx| {
- Tooltip::for_action(
- if prompt_metadata.default {
- "Remove from Default Prompt"
- } else {
- "Add to Default Prompt"
- },
- &ToggleDefaultPrompt,
- cx,
+ .items_end()
+ .gap_1()
+ .child(h_flex().h_8().font_family(buffer_font).when_some_else(
+ token_count,
+ |tokens_ready, token_count| {
+ tokens_ready.pr_3().justify_end().child(
+ // This isn't actually a button, it just let's us easily add
+ // a tooltip to the token count.
+ Button::new("token_count", token_count.clone())
+ .style(ButtonStyle::Transparent)
+ .color(Color::Muted)
+ .tooltip(move |cx| {
+ Tooltip::with_meta(
+ format!("{} tokens", token_count,),
+ None,
+ format!(
+ "Model: {}",
+ current_model.display_name()
+ ),
+ cx,
+ )
+ }),
)
- })
- .on_click(|_, cx| {
- cx.dispatch_action(Box::new(ToggleDefaultPrompt));
- }),
+ },
+ |tokens_loading| {
+ tokens_loading.w_12().justify_center().child(
+ Icon::new(IconName::ArrowCircle)
+ .size(IconSize::Small)
+ .color(Color::Muted)
+ .with_animation(
+ "arrow-circle",
+ Animation::new(Duration::from_secs(4)).repeat(),
+ |icon, delta| {
+ icon.transform(Transformation::rotate(
+ percentage(delta),
+ ))
+ },
+ ),
+ )
+ },
+ ))
+ .child(
+ h_flex().justify_center().w_12().h_8().child(
+ IconButton::new("toggle-default-prompt", IconName::Sparkle)
+ .style(ButtonStyle::Transparent)
+ .selected(prompt_metadata.default)
+ .selected_icon(IconName::SparkleFilled)
+ .icon_color(if prompt_metadata.default {
+ Color::Accent
+ } else {
+ Color::Muted
+ })
+ .shape(IconButtonShape::Square)
+ .tooltip(move |cx| {
+ Tooltip::text(
+ if prompt_metadata.default {
+ "Remove from Default Prompt"
+ } else {
+ "Add to Default Prompt"
+ },
+ cx,
+ )
+ })
+ .on_click(|_, cx| {
+ cx.dispatch_action(Box::new(ToggleDefaultPrompt));
+ }),
+ ),
)
.child(
- IconButton::new("delete-prompt", IconName::Trash)
- .shape(IconButtonShape::Square)
- .tooltip(move |cx| {
- Tooltip::for_action("Delete Prompt", &DeletePrompt, cx)
- })
- .on_click(|_, cx| {
- cx.dispatch_action(Box::new(DeletePrompt));
- }),
- )
- .children(prompt_editor.token_count.map(|token_count| {
- h_flex()
- .justify_center()
- .child(Label::new(token_count.to_string()))
- })),
+ h_flex().justify_center().w_12().h_8().child(
+ IconButton::new("delete-prompt", IconName::Trash)
+ .size(ButtonSize::Large)
+ .style(ButtonStyle::Transparent)
+ .shape(IconButtonShape::Square)
+ .tooltip(move |cx| {
+ Tooltip::for_action(
+ "Delete Prompt",
+ &DeletePrompt,
+ cx,
+ )
+ })
+ .on_click(|_, cx| {
+ cx.dispatch_action(Box::new(DeletePrompt));
+ }),
+ ),
+ ),
),
)
}))
diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs
index 7f87bfda7f..3ed0080fe5 100644
--- a/crates/picker/src/picker.rs
+++ b/crates/picker/src/picker.rs
@@ -103,6 +103,19 @@ pub trait PickerDelegate: Sized + 'static {
None
}
+ fn render_editor(&self, editor: &View, _cx: &mut ViewContext>) -> Div {
+ v_flex()
+ .child(
+ h_flex()
+ .overflow_hidden()
+ .flex_none()
+ .h_9()
+ .px_4()
+ .child(editor.clone()),
+ )
+ .child(Divider::horizontal())
+ }
+
fn render_match(
&self,
ix: usize,
@@ -552,16 +565,7 @@ impl Render for Picker {
.on_action(cx.listener(Self::use_selected_query))
.on_action(cx.listener(Self::confirm_input))
.child(match &self.head {
- Head::Editor(editor) => v_flex()
- .child(
- h_flex()
- .overflow_hidden()
- .flex_none()
- .h_9()
- .px_4()
- .child(editor.clone()),
- )
- .child(Divider::horizontal()),
+ Head::Editor(editor) => self.delegate.render_editor(&editor.clone(), cx),
Head::Empty(empty_head) => div().child(empty_head.clone()),
})
.when(self.delegate.match_count() > 0, |el| {
diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs
index 79b4d87750..cbda8cb12f 100644
--- a/crates/ui/src/components/icon.rs
+++ b/crates/ui/src/components/icon.rs
@@ -54,10 +54,14 @@ pub enum IconDecoration {
#[derive(Default, PartialEq, Copy, Clone)]
pub enum IconSize {
+ /// 10px
Indicator,
+ /// 12px
XSmall,
+ /// 14px
Small,
#[default]
+ /// 16px
Medium,
}
@@ -176,6 +180,8 @@ pub enum IconName {
Sliders,
Snip,
Space,
+ Sparkle,
+ SparkleFilled,
Spinner,
Split,
Star,
@@ -301,6 +307,8 @@ impl IconName {
IconName::Sliders => "icons/sliders.svg",
IconName::Snip => "icons/snip.svg",
IconName::Space => "icons/space.svg",
+ IconName::Sparkle => "icons/sparkle.svg",
+ IconName::SparkleFilled => "icons/sparkle_filled.svg",
IconName::Spinner => "icons/spinner.svg",
IconName::Split => "icons/split.svg",
IconName::Star => "icons/star.svg",