diff --git a/assets/icons/assist_15.svg b/assets/icons/assist_15.svg
new file mode 100644
index 0000000000..3baf8df3e9
--- /dev/null
+++ b/assets/icons/assist_15.svg
@@ -0,0 +1 @@
+
diff --git a/assets/icons/split_message_15.svg b/assets/icons/split_message_15.svg
new file mode 100644
index 0000000000..54d9e81224
--- /dev/null
+++ b/assets/icons/split_message_15.svg
@@ -0,0 +1 @@
+
diff --git a/crates/ai/src/assistant.rs b/crates/ai/src/assistant.rs
index 7dcd8830ed..929b54fded 100644
--- a/crates/ai/src/assistant.rs
+++ b/crates/ai/src/assistant.rs
@@ -28,7 +28,6 @@ use search::BufferSearchBar;
use serde::Deserialize;
use settings::SettingsStore;
use std::{
- borrow::Cow,
cell::RefCell,
cmp, env,
fmt::Write,
@@ -40,13 +39,9 @@ use std::{
time::Duration,
};
use theme::{ui::IconStyle, AssistantStyle};
-use util::{
- channel::ReleaseChannel, paths::CONVERSATIONS_DIR, post_inc, truncate_and_trailoff, ResultExt,
- TryFutureExt,
-};
+use util::{channel::ReleaseChannel, paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use workspace::{
dock::{DockPosition, Panel},
- item::Item,
searchable::Direction,
Save, ToggleZoom, Toolbar, Workspace,
};
@@ -361,64 +356,43 @@ impl AssistantPanel {
})
}
- fn render_current_model(
- &self,
- style: &AssistantStyle,
- cx: &mut ViewContext,
- ) -> Option> {
- enum Model {}
-
- let model = self
- .active_editor()?
- .read(cx)
- .conversation
- .read(cx)
- .model
- .clone();
-
- Some(
- MouseEventHandler::::new(0, cx, |state, _| {
- let style = style.model.style_for(state);
- Label::new(model, style.text.clone())
- .contained()
- .with_style(style.container)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, |_, this, cx| {
- if let Some(editor) = this.active_editor() {
- editor.update(cx, |editor, cx| {
- editor.cycle_model(cx);
- });
- }
- }),
- )
+ fn render_editor_tools(&self, style: &AssistantStyle) -> Vec> {
+ if self.active_editor().is_some() {
+ vec![
+ Self::render_split_button(&style.split_button).into_any(),
+ Self::render_assist_button(&style.assist_button).into_any(),
+ ]
+ } else {
+ Default::default()
+ }
}
- fn render_remaining_tokens(
- &self,
- style: &AssistantStyle,
- cx: &mut ViewContext,
- ) -> Option> {
- self.active_editor().and_then(|editor| {
- editor
- .read(cx)
- .conversation
- .read(cx)
- .remaining_tokens()
- .map(|remaining_tokens| {
- let remaining_tokens_style = if remaining_tokens <= 0 {
- &style.no_remaining_tokens
- } else {
- &style.remaining_tokens
- };
- Label::new(
- remaining_tokens.to_string(),
- remaining_tokens_style.text.clone(),
- )
- .contained()
- .with_style(remaining_tokens_style.container)
- })
- })
+ fn render_split_button(style: &IconStyle) -> impl Element {
+ enum SplitMessage {}
+ Svg::for_style(style.icon.clone())
+ .contained()
+ .with_style(style.container)
+ .mouse::(0)
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+ if let Some(active_editor) = this.active_editor() {
+ active_editor.update(cx, |editor, cx| editor.split(&Default::default(), cx));
+ }
+ })
+ }
+
+ fn render_assist_button(style: &IconStyle) -> impl Element {
+ enum Assist {}
+ Svg::for_style(style.icon.clone())
+ .contained()
+ .with_style(style.container)
+ .mouse::(0)
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
+ if let Some(active_editor) = this.active_editor() {
+ active_editor.update(cx, |editor, cx| editor.assist(&Default::default(), cx));
+ }
+ })
}
fn render_plus_button(style: &IconStyle) -> impl Element {
@@ -589,19 +563,16 @@ impl View for AssistantPanel {
)
.with_children(title)
.with_children(
- self.render_current_model(&style, cx)
- .map(|current_model| current_model.aligned().flex_float()),
- )
- .with_children(
- self.render_remaining_tokens(&style, cx)
- .map(|remaining_tokens| remaining_tokens.aligned().flex_float()),
+ self.render_editor_tools(&style)
+ .into_iter()
+ .map(|tool| tool.aligned().flex_float()),
)
.with_child(
Self::render_plus_button(&style.plus_button)
.aligned()
.flex_float(),
)
- .with_child(self.render_zoom_button(&style, cx).aligned().flex_float())
+ .with_child(self.render_zoom_button(&style, cx).aligned())
.contained()
.with_style(theme.workspace.tab_bar.container)
.expanded()
@@ -1995,6 +1966,44 @@ impl ConversationEditor {
.map(|summary| summary.text.clone())
.unwrap_or_else(|| "New Conversation".into())
}
+
+ fn render_current_model(
+ &self,
+ style: &AssistantStyle,
+ cx: &mut ViewContext,
+ ) -> impl Element {
+ enum Model {}
+
+ MouseEventHandler::::new(0, cx, |state, cx| {
+ let style = style.model.style_for(state);
+ Label::new(self.conversation.read(cx).model.clone(), style.text.clone())
+ .contained()
+ .with_style(style.container)
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, |_, this, cx| this.cycle_model(cx))
+ }
+
+ fn render_remaining_tokens(
+ &self,
+ style: &AssistantStyle,
+ cx: &mut ViewContext,
+ ) -> Option> {
+ let remaining_tokens = self.conversation.read(cx).remaining_tokens()?;
+ let remaining_tokens_style = if remaining_tokens <= 0 {
+ &style.no_remaining_tokens
+ } else {
+ &style.remaining_tokens
+ };
+ Some(
+ Label::new(
+ remaining_tokens.to_string(),
+ remaining_tokens_style.text.clone(),
+ )
+ .contained()
+ .with_style(remaining_tokens_style.container),
+ )
+ }
}
impl Entity for ConversationEditor {
@@ -2008,9 +2017,20 @@ impl View for ConversationEditor {
fn render(&mut self, cx: &mut ViewContext) -> AnyElement {
let theme = &theme::current(cx).assistant;
- ChildView::new(&self.editor, cx)
- .contained()
- .with_style(theme.container)
+ Stack::new()
+ .with_child(
+ ChildView::new(&self.editor, cx)
+ .contained()
+ .with_style(theme.container),
+ )
+ .with_child(
+ Flex::row()
+ .with_child(self.render_current_model(theme, cx))
+ .with_children(self.render_remaining_tokens(theme, cx))
+ .aligned()
+ .top()
+ .right(),
+ )
.into_any()
}
@@ -2021,29 +2041,6 @@ impl View for ConversationEditor {
}
}
-impl Item for ConversationEditor {
- fn tab_content(
- &self,
- _: Option,
- style: &theme::Tab,
- cx: &gpui::AppContext,
- ) -> AnyElement {
- let title = truncate_and_trailoff(&self.title(cx), editor::MAX_TAB_TITLE_LEN);
- Label::new(title, style.label.clone()).into_any()
- }
-
- fn tab_tooltip_text(&self, cx: &AppContext) -> Option> {
- Some(self.title(cx).into())
- }
-
- fn as_searchable(
- &self,
- _: &ViewHandle,
- ) -> Option> {
- Some(Box::new(self.editor.clone()))
- }
-}
-
#[derive(Clone, Debug)]
struct MessageAnchor {
id: MessageId,
diff --git a/crates/gpui/src/elements/label.rs b/crates/gpui/src/elements/label.rs
index 57aeba2886..d9cf537333 100644
--- a/crates/gpui/src/elements/label.rs
+++ b/crates/gpui/src/elements/label.rs
@@ -165,6 +165,7 @@ impl Element for Label {
_: &mut V,
cx: &mut ViewContext,
) -> Self::PaintState {
+ let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
line.paint(
scene,
bounds.origin(),
diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs
index c012e04807..7a6b554247 100644
--- a/crates/theme/src/theme.rs
+++ b/crates/theme/src/theme.rs
@@ -994,6 +994,8 @@ pub struct TerminalStyle {
pub struct AssistantStyle {
pub container: ContainerStyle,
pub hamburger_button: IconStyle,
+ pub split_button: IconStyle,
+ pub assist_button: IconStyle,
pub zoom_in_button: IconStyle,
pub zoom_out_button: IconStyle,
pub plus_button: IconStyle,
@@ -1003,7 +1005,6 @@ pub struct AssistantStyle {
pub user_sender: Interactive,
pub assistant_sender: Interactive,
pub system_sender: Interactive,
- pub model_info_container: ContainerStyle,
pub model: Interactive,
pub remaining_tokens: ContainedText,
pub no_remaining_tokens: ContainedText,
diff --git a/styles/src/styleTree/assistant.ts b/styles/src/styleTree/assistant.ts
index abdd55818b..153b2f9e42 100644
--- a/styles/src/styleTree/assistant.ts
+++ b/styles/src/styleTree/assistant.ts
@@ -28,6 +28,32 @@ export default function assistant(colorScheme: ColorScheme) {
margin: { left: 12 },
}
},
+ splitButton: {
+ icon: {
+ color: text(layer, "sans", "default", { size: "sm" }).color,
+ asset: "icons/split_message_15.svg",
+ dimensions: {
+ width: 15,
+ height: 15,
+ },
+ },
+ container: {
+ margin: { left: 12 },
+ }
+ },
+ assistButton: {
+ icon: {
+ color: text(layer, "sans", "default", { size: "sm" }).color,
+ asset: "icons/assist_15.svg",
+ dimensions: {
+ width: 15,
+ height: 15,
+ },
+ },
+ container: {
+ margin: { left: 12, right: 12 },
+ }
+ },
zoomInButton: {
icon: {
color: text(layer, "sans", "default", { size: "sm" }).color,
@@ -120,13 +146,10 @@ export default function assistant(colorScheme: ColorScheme) {
margin: { top: 2, left: 8 },
...text(layer, "sans", "default", { size: "2xs" }),
},
- modelInfoContainer: {
- margin: { right: 16, top: 4 },
- },
model: interactive({
base: {
background: background(layer, "on"),
- margin: { right: 8 },
+ margin: { left: 12, right: 12, top: 12 },
padding: 4,
cornerRadius: 4,
...text(layer, "sans", "default", { size: "xs" }),
@@ -139,11 +162,17 @@ export default function assistant(colorScheme: ColorScheme) {
},
}),
remainingTokens: {
- margin: { right: 12 },
+ background: background(layer, "on"),
+ margin: { top: 12, right: 12 },
+ padding: 4,
+ cornerRadius: 4,
...text(layer, "sans", "positive", { size: "xs" }),
},
noRemainingTokens: {
- margin: { right: 12 },
+ background: background(layer, "on"),
+ margin: { top: 12, right: 12 },
+ padding: 4,
+ cornerRadius: 4,
...text(layer, "sans", "negative", { size: "xs" }),
},
errorIcon: {