Move model and remaining tokens to assistant toolbar

This commit is contained in:
Nathan Sobo 2023-06-21 19:01:30 -06:00
parent d78fbbc63e
commit a75341db97
3 changed files with 106 additions and 79 deletions

View file

@ -85,7 +85,7 @@
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'. // Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right", "dock": "right",
// Default width when the assistant is docked to the left or right. // Default width when the assistant is docked to the left or right.
"default_width": 450, "default_width": 640,
// Default height when the assistant is docked to the bottom. // Default height when the assistant is docked to the bottom.
"default_height": 320 "default_height": 320
}, },

View file

@ -38,7 +38,7 @@ use std::{
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}; };
use theme::ui::IconStyle; use theme::{ui::IconStyle, AssistantStyle};
use util::{ use util::{
channel::ReleaseChannel, paths::CONVERSATIONS_DIR, post_inc, truncate_and_trailoff, ResultExt, channel::ReleaseChannel, paths::CONVERSATIONS_DIR, post_inc, truncate_and_trailoff, ResultExt,
TryFutureExt, TryFutureExt,
@ -112,8 +112,8 @@ pub enum AssistantPanelEvent {
pub struct AssistantPanel { pub struct AssistantPanel {
width: Option<f32>, width: Option<f32>,
height: Option<f32>, height: Option<f32>,
active_conversation_index: Option<usize>, active_editor_index: Option<usize>,
conversation_editors: Vec<ViewHandle<ConversationEditor>>, editors: Vec<ViewHandle<ConversationEditor>>,
saved_conversations: Vec<SavedConversationMetadata>, saved_conversations: Vec<SavedConversationMetadata>,
saved_conversations_list_state: UniformListState, saved_conversations_list_state: UniformListState,
zoomed: bool, zoomed: bool,
@ -162,8 +162,8 @@ impl AssistantPanel {
}); });
let mut this = Self { let mut this = Self {
active_conversation_index: Default::default(), active_editor_index: Default::default(),
conversation_editors: Default::default(), editors: Default::default(),
saved_conversations, saved_conversations,
saved_conversations_list_state: Default::default(), saved_conversations_list_state: Default::default(),
zoomed: false, zoomed: false,
@ -216,8 +216,12 @@ impl AssistantPanel {
self.subscriptions self.subscriptions
.push(cx.subscribe(&editor, Self::handle_conversation_editor_event)); .push(cx.subscribe(&editor, Self::handle_conversation_editor_event));
self.active_conversation_index = Some(self.conversation_editors.len()); let conversation = editor.read(cx).conversation.clone();
self.conversation_editors.push(editor.clone()); self.subscriptions
.push(cx.observe(&conversation, |_, _, cx| cx.notify()));
self.active_editor_index = Some(self.editors.len());
self.editors.push(editor.clone());
if self.has_focus(cx) { if self.has_focus(cx) {
cx.focus(&editor); cx.focus(&editor);
} }
@ -271,9 +275,8 @@ impl AssistantPanel {
} }
} }
fn active_conversation_editor(&self) -> Option<&ViewHandle<ConversationEditor>> { fn active_editor(&self) -> Option<&ViewHandle<ConversationEditor>> {
self.conversation_editors self.editors.get(self.active_editor_index?)
.get(self.active_conversation_index?)
} }
fn render_hamburger_button(style: &IconStyle) -> impl Element<Self> { fn render_hamburger_button(style: &IconStyle) -> impl Element<Self> {
@ -284,11 +287,71 @@ impl AssistantPanel {
.mouse::<ListConversations>(0) .mouse::<ListConversations>(0)
.with_cursor_style(CursorStyle::PointingHand) .with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this: &mut Self, cx| { .on_click(MouseButton::Left, |_, this: &mut Self, cx| {
this.active_conversation_index = None; this.active_editor_index = None;
cx.notify(); cx.notify();
}) })
} }
fn render_current_model(
&self,
style: &AssistantStyle,
cx: &mut ViewContext<Self>,
) -> Option<impl Element<Self>> {
enum Model {}
let model = self
.active_editor()?
.read(cx)
.conversation
.read(cx)
.model
.clone();
Some(
MouseEventHandler::<Model, _>::new(0, cx, |state, _| {
let style = style.model.style_for(state, false);
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_remaining_tokens(
&self,
style: &AssistantStyle,
cx: &mut ViewContext<Self>,
) -> Option<impl Element<Self>> {
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_plus_button(style: &IconStyle) -> impl Element<Self> { fn render_plus_button(style: &IconStyle) -> impl Element<Self> {
enum AddConversation {} enum AddConversation {}
Svg::for_style(style.icon.clone()) Svg::for_style(style.icon.clone())
@ -337,8 +400,8 @@ impl AssistantPanel {
} }
fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> { fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
if let Some(ix) = self.conversation_editor_index_for_path(&path, cx) { if let Some(ix) = self.editor_index_for_path(&path, cx) {
self.active_conversation_index = Some(ix); self.active_editor_index = Some(ix);
cx.notify(); cx.notify();
return Task::ready(Ok(())); return Task::ready(Ok(()));
} }
@ -356,8 +419,8 @@ impl AssistantPanel {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
// If, by the time we've loaded the conversation, the user has already opened // If, by the time we've loaded the conversation, the user has already opened
// the same conversation, we don't want to open it again. // the same conversation, we don't want to open it again.
if let Some(ix) = this.conversation_editor_index_for_path(&path, cx) { if let Some(ix) = this.editor_index_for_path(&path, cx) {
this.active_conversation_index = Some(ix); this.active_editor_index = Some(ix);
} else { } else {
let editor = cx let editor = cx
.add_view(|cx| ConversationEditor::from_conversation(conversation, fs, cx)); .add_view(|cx| ConversationEditor::from_conversation(conversation, fs, cx));
@ -368,8 +431,8 @@ impl AssistantPanel {
}) })
} }
fn conversation_editor_index_for_path(&self, path: &Path, cx: &AppContext) -> Option<usize> { fn editor_index_for_path(&self, path: &Path, cx: &AppContext) -> Option<usize> {
self.conversation_editors self.editors
.iter() .iter()
.position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path)) .position(|editor| editor.read(cx).conversation.read(cx).path.as_deref() == Some(path))
} }
@ -418,11 +481,13 @@ impl View for AssistantPanel {
.aligned() .aligned()
.into_any() .into_any()
} else { } else {
let title = self.active_conversation_editor().map(|editor| { let title = self.active_editor().map(|editor| {
Label::new(editor.read(cx).title(cx), style.title.text.clone()) Label::new(editor.read(cx).title(cx), style.title.text.clone())
.contained() .contained()
.with_style(style.title.container) .with_style(style.title.container)
.aligned() .aligned()
.left()
.flex(1., false)
}); });
Flex::column() Flex::column()
@ -432,6 +497,14 @@ impl View for AssistantPanel {
Self::render_hamburger_button(&style.hamburger_button).aligned(), Self::render_hamburger_button(&style.hamburger_button).aligned(),
) )
.with_children(title) .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()),
)
.with_child( .with_child(
Self::render_plus_button(&style.plus_button) Self::render_plus_button(&style.plus_button)
.aligned() .aligned()
@ -443,7 +516,7 @@ impl View for AssistantPanel {
.constrained() .constrained()
.with_height(theme.workspace.tab_bar.height), .with_height(theme.workspace.tab_bar.height),
) )
.with_child(if let Some(editor) = self.active_conversation_editor() { .with_child(if let Some(editor) = self.active_editor() {
ChildView::new(editor, cx).flex(1., true).into_any() ChildView::new(editor, cx).flex(1., true).into_any()
} else { } else {
UniformList::new( UniformList::new(
@ -466,7 +539,7 @@ impl View for AssistantPanel {
fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) { fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
self.has_focus = true; self.has_focus = true;
if cx.is_self_focused() { if cx.is_self_focused() {
if let Some(editor) = self.active_conversation_editor() { if let Some(editor) = self.active_editor() {
cx.focus(editor); cx.focus(editor);
} else if let Some(api_key_editor) = self.api_key_editor.as_ref() { } else if let Some(api_key_editor) = self.api_key_editor.as_ref() {
cx.focus(api_key_editor); cx.focus(api_key_editor);
@ -562,7 +635,7 @@ impl Panel for AssistantPanel {
} }
} }
if self.conversation_editors.is_empty() { if self.editors.is_empty() {
self.new_conversation(cx); self.new_conversation(cx);
} }
} }
@ -1700,7 +1773,7 @@ impl ConversationEditor {
if let Some(text) = text { if let Some(text) = text {
panel.update(cx, |panel, cx| { panel.update(cx, |panel, cx| {
let conversation = panel let conversation = panel
.active_conversation_editor() .active_editor()
.cloned() .cloned()
.unwrap_or_else(|| panel.new_conversation(cx)); .unwrap_or_else(|| panel.new_conversation(cx));
conversation.update(cx, |conversation, cx| { conversation.update(cx, |conversation, cx| {
@ -1781,7 +1854,7 @@ impl ConversationEditor {
.summary .summary
.as_ref() .as_ref()
.map(|summary| summary.text.clone()) .map(|summary| summary.text.clone())
.unwrap_or_else(|| "New Context".into()) .unwrap_or_else(|| "New Conversation".into())
} }
} }
@ -1795,49 +1868,10 @@ impl View for ConversationEditor {
} }
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> { fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
enum Model {}
let theme = &theme::current(cx).assistant; let theme = &theme::current(cx).assistant;
let conversation = self.conversation.read(cx); ChildView::new(&self.editor, cx)
let model = conversation.model.clone();
let remaining_tokens = conversation.remaining_tokens().map(|remaining_tokens| {
let remaining_tokens_style = if remaining_tokens <= 0 {
&theme.no_remaining_tokens
} else {
&theme.remaining_tokens
};
Label::new(
remaining_tokens.to_string(),
remaining_tokens_style.text.clone(),
)
.contained() .contained()
.with_style(remaining_tokens_style.container) .with_style(theme.container)
});
Stack::new()
.with_child(
ChildView::new(&self.editor, cx)
.contained()
.with_style(theme.container),
)
.with_child(
Flex::row()
.with_child(
MouseEventHandler::<Model, _>::new(0, cx, |state, _| {
let style = theme.model.style_for(state, false);
Label::new(model, style.text.clone())
.contained()
.with_style(style.container)
})
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, |_, this, cx| this.cycle_model(cx)),
)
.with_children(remaining_tokens)
.contained()
.with_style(theme.model_info_container)
.aligned()
.top()
.right(),
)
.into_any() .into_any()
} }

View file

@ -24,7 +24,7 @@ export default function assistant(colorScheme: ColorScheme) {
}, },
}, },
container: { container: {
margin: { left: 8 }, margin: { left: 12 },
} }
}, },
plusButton: { plusButton: {
@ -37,11 +37,11 @@ export default function assistant(colorScheme: ColorScheme) {
}, },
}, },
container: { container: {
margin: { right: 8 }, margin: { right: 12 },
} }
}, },
title: { title: {
margin: { left: 8 }, margin: { left: 12 },
...text(layer, "sans", "default", { size: "sm" }) ...text(layer, "sans", "default", { size: "sm" })
}, },
savedConversation: { savedConversation: {
@ -76,28 +76,21 @@ export default function assistant(colorScheme: ColorScheme) {
}, },
model: { model: {
background: background(layer, "on"), background: background(layer, "on"),
border: border(layer, "on", { overlay: true }), margin: { right: 8 },
padding: 4, padding: 4,
cornerRadius: 4, cornerRadius: 4,
...text(layer, "sans", "default", { size: "xs" }), ...text(layer, "sans", "default", { size: "xs" }),
hover: { hover: {
background: background(layer, "on", "hovered"), background: background(layer, "on", "hovered"),
border: border(layer, "on", { overlay: true }),
}, },
}, },
remainingTokens: { remainingTokens: {
background: background(layer, "on"), margin: { right: 12 },
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "positive", { size: "xs" }), ...text(layer, "sans", "positive", { size: "xs" }),
}, },
noRemainingTokens: { noRemainingTokens: {
background: background(layer, "on"), margin: { right: 12 },
border: border(layer, "on", { overlay: true }),
padding: 4,
margin: { left: 4 },
cornerRadius: 4,
...text(layer, "sans", "negative", { size: "xs" }), ...text(layer, "sans", "negative", { size: "xs" }),
}, },
errorIcon: { errorIcon: {