assistant2: Render saved conversations inline instead of in a modal (#11630)

This PR reworks how saved conversations are rendered in the new
assistant panel.

Instead of rendering them in a modal we now display them in the panel
itself:

<img width="402" alt="Screenshot 2024-05-09 at 6 18 40 PM"
src="https://github.com/zed-industries/zed/assets/1486634/82decc04-cb31-4d83-a942-7e8426e02679">

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2024-05-09 18:29:08 -04:00 committed by GitHub
parent a3e75540af
commit 27ed0f4273
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 67 deletions

View File

@ -2,10 +2,11 @@ mod assistant_settings;
mod attachments;
mod completion_provider;
mod saved_conversation;
mod saved_conversation_picker;
mod saved_conversations;
mod tools;
pub mod ui;
use crate::saved_conversation::SavedConversationMetadata;
use crate::ui::UserOrAssistant;
use ::ui::{div, prelude::*, Color, Tooltip, ViewContext};
use anyhow::{Context, Result};
@ -29,7 +30,7 @@ use language::{language_settings::SoftWrap, LanguageRegistry};
use open_ai::{FunctionContent, ToolCall, ToolCallContent};
use rich_text::RichText;
use saved_conversation::{SavedAssistantMessagePart, SavedChatMessage, SavedConversation};
use saved_conversation_picker::SavedConversationPicker;
use saved_conversations::SavedConversations;
use semantic_index::{CloudEmbeddingProvider, ProjectIndex, ProjectIndexDebugView, SemanticIndex};
use serde::{Deserialize, Serialize};
use settings::Settings;
@ -61,15 +62,7 @@ pub enum SubmitMode {
Codebase,
}
gpui::actions!(
assistant2,
[
Cancel,
ToggleFocus,
DebugProjectIndex,
ToggleSavedConversations
]
);
gpui::actions!(assistant2, [Cancel, ToggleFocus, DebugProjectIndex,]);
gpui::impl_actions!(assistant2, [Submit]);
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
@ -109,8 +102,6 @@ pub fn init(client: Arc<Client>, cx: &mut AppContext) {
},
)
.detach();
cx.observe_new_views(SavedConversationPicker::register)
.detach();
}
pub fn enabled(cx: &AppContext) -> bool {
@ -262,6 +253,8 @@ pub struct AssistantChat {
fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>,
composer_editor: View<Editor>,
saved_conversations: View<SavedConversations>,
saved_conversations_open: bool,
project_index_button: View<ProjectIndexButton>,
active_file_button: Option<View<ActiveFileButton>>,
user_store: Model<UserStore>,
@ -317,6 +310,24 @@ impl AssistantChat {
_ => None,
};
let saved_conversations = cx.new_view(|cx| SavedConversations::new(cx));
cx.spawn({
let fs = fs.clone();
let saved_conversations = saved_conversations.downgrade();
|_assistant_chat, mut cx| async move {
let saved_conversation_metadata = SavedConversationMetadata::list(fs).await?;
cx.update(|cx| {
saved_conversations.update(cx, |this, cx| {
this.init(saved_conversation_metadata, cx);
})
})??;
anyhow::Ok(())
}
})
.detach_and_log_err(cx);
Self {
model,
messages: Vec::new(),
@ -326,6 +337,8 @@ impl AssistantChat {
editor.set_placeholder_text("Send a message…", cx);
editor
}),
saved_conversations,
saved_conversations_open: false,
list_state,
user_store,
fs,
@ -357,6 +370,10 @@ impl AssistantChat {
})
}
fn toggle_saved_conversations(&mut self) {
self.saved_conversations_open = !self.saved_conversations_open;
}
fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
// If we're currently editing a message, cancel the edit.
if let Some(editing_message) = self.editing_message.take() {
@ -1017,18 +1034,18 @@ impl Render for AssistantChat {
.h(header_height)
.p(Spacing::Small.rems(cx))
.child(
IconButton::new("open-saved-conversations", IconName::ChevronLeft)
.on_click(|_event, cx| {
cx.dispatch_action(Box::new(ToggleSavedConversations))
})
.tooltip(move |cx| {
Tooltip::with_meta(
"Switch Conversations",
Some(&ToggleSavedConversations),
"UI will change, temporary.",
cx,
)
}),
IconButton::new(
"toggle-saved-conversations",
if self.saved_conversations_open {
IconName::ChevronRight
} else {
IconName::ChevronLeft
},
)
.on_click(cx.listener(|this, _event, _cx| {
this.toggle_saved_conversations();
}))
.tooltip(move |cx| Tooltip::text("Switch Conversations", cx)),
)
.child(
h_flex()
@ -1052,6 +1069,15 @@ impl Render for AssistantChat {
),
),
)
.when(self.saved_conversations_open, |element| {
element.child(
h_flex()
.absolute()
.top(header_height)
.w_full()
.child(self.saved_conversations.clone()),
)
})
.child(Composer::new(
self.composer_editor.clone(),
self.project_index_button.clone(),

View File

@ -5,65 +5,56 @@ use gpui::{AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, V
use picker::{Picker, PickerDelegate};
use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
use util::ResultExt;
use workspace::{ModalView, Workspace};
use crate::saved_conversation::SavedConversationMetadata;
use crate::ToggleSavedConversations;
pub struct SavedConversationPicker {
picker: View<Picker<SavedConversationPickerDelegate>>,
pub struct SavedConversations {
focus_handle: FocusHandle,
picker: Option<View<Picker<SavedConversationPickerDelegate>>>,
}
impl EventEmitter<DismissEvent> for SavedConversationPicker {}
impl EventEmitter<DismissEvent> for SavedConversations {}
impl ModalView for SavedConversationPicker {}
impl FocusableView for SavedConversationPicker {
impl FocusableView for SavedConversations {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.picker.focus_handle(cx)
if let Some(picker) = self.picker.as_ref() {
picker.focus_handle(cx)
} else {
self.focus_handle.clone()
}
}
}
impl SavedConversationPicker {
pub fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
workspace.register_action(|workspace, _: &ToggleSavedConversations, cx| {
let fs = workspace.project().read(cx).fs().clone();
cx.spawn(|workspace, mut cx| async move {
let saved_conversations = SavedConversationMetadata::list(fs).await?;
cx.update(|cx| {
workspace.update(cx, |workspace, cx| {
workspace.toggle_modal(cx, move |cx| {
let delegate = SavedConversationPickerDelegate::new(
cx.view().downgrade(),
saved_conversations,
);
Self::new(delegate, cx)
});
})
})??;
anyhow::Ok(())
})
.detach_and_log_err(cx);
});
impl SavedConversations {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
picker: None,
}
}
pub fn new(delegate: SavedConversationPickerDelegate, cx: &mut ViewContext<Self>) -> Self {
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx));
Self { picker }
pub fn init(
&mut self,
saved_conversations: Vec<SavedConversationMetadata>,
cx: &mut ViewContext<Self>,
) {
let delegate =
SavedConversationPickerDelegate::new(cx.view().downgrade(), saved_conversations);
self.picker = Some(cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false)));
}
}
impl Render for SavedConversationPicker {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex().w(rems(34.)).child(self.picker.clone())
impl Render for SavedConversations {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.w_full()
.bg(cx.theme().colors().panel_background)
.children(self.picker.clone())
}
}
pub struct SavedConversationPickerDelegate {
view: WeakView<SavedConversationPicker>,
view: WeakView<SavedConversations>,
saved_conversations: Vec<SavedConversationMetadata>,
selected_index: usize,
matches: Vec<StringMatch>,
@ -71,7 +62,7 @@ pub struct SavedConversationPickerDelegate {
impl SavedConversationPickerDelegate {
pub fn new(
weak_view: WeakView<SavedConversationPicker>,
weak_view: WeakView<SavedConversations>,
saved_conversations: Vec<SavedConversationMetadata>,
) -> Self {
let matches = saved_conversations
@ -194,7 +185,6 @@ impl PickerDelegate for SavedConversationPickerDelegate {
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
.child(HighlightedLabel::new(