assistant2: Add prompt editor history (#23439)

This PR adds the prompt editor history to Assistant2.

<img width="1309" alt="Screenshot 2025-01-21 at 9 02 07 PM"
src="https://github.com/user-attachments/assets/d79936fe-1c23-425f-b99d-43f85afd0c39"
/>

Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2025-01-21 21:11:48 -05:00 committed by GitHub
parent be407e27f9
commit 7516b8c8b7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 163 additions and 32 deletions

View file

@ -1342,7 +1342,6 @@ impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
return; return;
}; };
// Activate the panel
if !panel.focus_handle(cx).contains_focused(cx) { if !panel.focus_handle(cx).contains_focused(cx) {
workspace.toggle_panel_focus::<AssistantPanel>(cx); workspace.toggle_panel_focus::<AssistantPanel>(cx);
} }

View file

@ -41,6 +41,7 @@ actions!(
ToggleModelSelector, ToggleModelSelector,
RemoveAllContext, RemoveAllContext,
OpenHistory, OpenHistory,
OpenPromptEditorHistory,
RemoveSelectedThread, RemoveSelectedThread,
Chat, Chat,
ChatMode, ChatMode,

View file

@ -1,7 +1,10 @@
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Result; use anyhow::{anyhow, Result};
use assistant_context_editor::{make_lsp_adapter_delegate, ContextEditor}; use assistant_context_editor::{
make_lsp_adapter_delegate, AssistantPanelDelegate, ContextEditor, ContextHistory,
};
use assistant_settings::{AssistantDockPosition, AssistantSettings}; use assistant_settings::{AssistantDockPosition, AssistantSettings};
use assistant_slash_command::SlashCommandWorkingSet; use assistant_slash_command::SlashCommandWorkingSet;
use assistant_tool::ToolWorkingSet; use assistant_tool::ToolWorkingSet;
@ -13,6 +16,7 @@ use gpui::{
WindowContext, WindowContext,
}; };
use language::LanguageRegistry; use language::LanguageRegistry;
use project::Project;
use prompt_library::PromptBuilder; use prompt_library::PromptBuilder;
use settings::Settings; use settings::Settings;
use time::UtcOffset; use time::UtcOffset;
@ -26,9 +30,10 @@ use crate::message_editor::MessageEditor;
use crate::thread::{Thread, ThreadError, ThreadId}; use crate::thread::{Thread, ThreadError, ThreadId};
use crate::thread_history::{PastThread, ThreadHistory}; use crate::thread_history::{PastThread, ThreadHistory};
use crate::thread_store::ThreadStore; use crate::thread_store::ThreadStore;
use crate::{NewPromptEditor, NewThread, OpenHistory, ToggleFocus}; use crate::{NewPromptEditor, NewThread, OpenHistory, OpenPromptEditorHistory, ToggleFocus};
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
<dyn AssistantPanelDelegate>::set_global(Arc::new(ConcreteAssistantPanelDelegate), cx);
cx.observe_new_views( cx.observe_new_views(
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| { |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
workspace workspace
@ -50,7 +55,13 @@ pub fn init(cx: &mut AppContext) {
.register_action(|workspace, _: &NewPromptEditor, cx| { .register_action(|workspace, _: &NewPromptEditor, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) { if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx); workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.new_prompt_editor(workspace, cx)); panel.update(cx, |panel, cx| panel.new_prompt_editor(cx));
}
})
.register_action(|workspace, _: &OpenPromptEditorHistory, cx| {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
workspace.focus_panel::<AssistantPanel>(cx);
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(cx));
} }
}); });
}, },
@ -62,10 +73,12 @@ enum ActiveView {
Thread, Thread,
PromptEditor, PromptEditor,
History, History,
PromptEditorHistory,
} }
pub struct AssistantPanel { pub struct AssistantPanel {
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
project: Model<Project>,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
language_registry: Arc<LanguageRegistry>, language_registry: Arc<LanguageRegistry>,
thread_store: Model<ThreadStore>, thread_store: Model<ThreadStore>,
@ -73,11 +86,13 @@ pub struct AssistantPanel {
message_editor: View<MessageEditor>, message_editor: View<MessageEditor>,
context_store: Model<assistant_context_editor::ContextStore>, context_store: Model<assistant_context_editor::ContextStore>,
context_editor: Option<View<ContextEditor>>, context_editor: Option<View<ContextEditor>>,
context_history: Option<View<ContextHistory>>,
tools: Arc<ToolWorkingSet>, tools: Arc<ToolWorkingSet>,
local_timezone: UtcOffset, local_timezone: UtcOffset,
active_view: ActiveView, active_view: ActiveView,
history: View<ThreadHistory>, history: View<ThreadHistory>,
new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>, new_item_context_menu_handle: PopoverMenuHandle<ContextMenu>,
open_history_context_menu_handle: PopoverMenuHandle<ContextMenu>,
width: Option<Pixels>, width: Option<Pixels>,
height: Option<Pixels>, height: Option<Pixels>,
} }
@ -126,7 +141,8 @@ impl AssistantPanel {
) -> Self { ) -> Self {
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx)); let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let language_registry = workspace.project().read(cx).languages().clone(); let project = workspace.project().clone();
let language_registry = project.read(cx).languages().clone();
let workspace = workspace.weak_handle(); let workspace = workspace.weak_handle();
let weak_self = cx.view().downgrade(); let weak_self = cx.view().downgrade();
@ -143,6 +159,7 @@ impl AssistantPanel {
Self { Self {
active_view: ActiveView::Thread, active_view: ActiveView::Thread,
workspace: workspace.clone(), workspace: workspace.clone(),
project,
fs: fs.clone(), fs: fs.clone(),
language_registry: language_registry.clone(), language_registry: language_registry.clone(),
thread_store: thread_store.clone(), thread_store: thread_store.clone(),
@ -158,6 +175,7 @@ impl AssistantPanel {
message_editor, message_editor,
context_store, context_store,
context_editor: None, context_editor: None,
context_history: None,
tools, tools,
local_timezone: UtcOffset::from_whole_seconds( local_timezone: UtcOffset::from_whole_seconds(
chrono::Local::now().offset().local_minus_utc(), chrono::Local::now().offset().local_minus_utc(),
@ -165,6 +183,7 @@ impl AssistantPanel {
.unwrap(), .unwrap(),
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)), history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
new_item_context_menu_handle: PopoverMenuHandle::default(), new_item_context_menu_handle: PopoverMenuHandle::default(),
open_history_context_menu_handle: PopoverMenuHandle::default(),
width: None, width: None,
height: None, height: None,
} }
@ -210,21 +229,22 @@ impl AssistantPanel {
self.message_editor.focus_handle(cx).focus(cx); self.message_editor.focus_handle(cx).focus(cx);
} }
fn new_prompt_editor(&mut self, workspace: &mut Workspace, cx: &mut ViewContext<Self>) { fn new_prompt_editor(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::PromptEditor; self.active_view = ActiveView::PromptEditor;
let project = workspace.project().clone();
let context = self let context = self
.context_store .context_store
.update(cx, |context_store, cx| context_store.create(cx)); .update(cx, |context_store, cx| context_store.create(cx));
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten(); let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
.log_err()
.flatten();
self.context_editor = Some(cx.new_view(|cx| { self.context_editor = Some(cx.new_view(|cx| {
ContextEditor::for_context( ContextEditor::for_context(
context, context,
self.fs.clone(), self.fs.clone(),
self.workspace.clone(), self.workspace.clone(),
project, self.project.clone(),
lsp_adapter_delegate, lsp_adapter_delegate,
cx, cx,
) )
@ -241,6 +261,60 @@ impl AssistantPanel {
cx.notify(); cx.notify();
} }
fn open_prompt_editor_history(&mut self, cx: &mut ViewContext<Self>) {
self.active_view = ActiveView::PromptEditorHistory;
self.context_history = Some(cx.new_view(|cx| {
ContextHistory::new(
self.project.clone(),
self.context_store.clone(),
self.workspace.clone(),
cx,
)
}));
if let Some(context_history) = self.context_history.as_ref() {
context_history.focus_handle(cx).focus(cx);
}
cx.notify();
}
fn open_saved_prompt_editor(
&mut self,
path: PathBuf,
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
let context = self
.context_store
.update(cx, |store, cx| store.open_local_context(path.clone(), cx));
let fs = self.fs.clone();
let project = self.project.clone();
let workspace = self.workspace.clone();
let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
cx.spawn(|this, mut cx| async move {
let context = context.await?;
this.update(&mut cx, |this, cx| {
let editor = cx.new_view(|cx| {
ContextEditor::for_context(
context,
fs,
workspace,
project,
lsp_adapter_delegate,
cx,
)
});
this.active_view = ActiveView::PromptEditor;
this.context_editor = Some(editor);
anyhow::Ok(())
})??;
Ok(())
})
}
pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) { pub(crate) fn open_thread(&mut self, thread_id: &ThreadId, cx: &mut ViewContext<Self>) {
let Some(thread) = self let Some(thread) = self
.thread_store .thread_store
@ -285,8 +359,21 @@ impl FocusableView for AssistantPanel {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle { fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
match self.active_view { match self.active_view {
ActiveView::Thread => self.message_editor.focus_handle(cx), ActiveView::Thread => self.message_editor.focus_handle(cx),
ActiveView::PromptEditor => self.context_editor.as_ref().unwrap().focus_handle(cx),
ActiveView::History => self.history.focus_handle(cx), ActiveView::History => self.history.focus_handle(cx),
ActiveView::PromptEditor => {
if let Some(context_editor) = self.context_editor.as_ref() {
context_editor.focus_handle(cx)
} else {
cx.focus_handle()
}
}
ActiveView::PromptEditorHistory => {
if let Some(context_history) = self.context_history.as_ref() {
context_history.focus_handle(cx)
} else {
cx.focus_handle()
}
}
} }
} }
} }
@ -369,8 +456,6 @@ impl Panel for AssistantPanel {
impl AssistantPanel { impl AssistantPanel {
fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_toolbar(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let thread = self.thread.read(cx); let thread = self.thread.read(cx);
let title = match self.active_view { let title = match self.active_view {
@ -390,7 +475,8 @@ impl AssistantPanel {
SharedString::from(context_editor.read(cx).title(cx).to_string()) SharedString::from(context_editor.read(cx).title(cx).to_string())
}) })
.unwrap_or_else(|| SharedString::from("Loading Summary…")), .unwrap_or_else(|| SharedString::from("Loading Summary…")),
ActiveView::History => "History".into(), ActiveView::History => "History / Thread".into(),
ActiveView::PromptEditorHistory => "History / Prompt Editor".into(),
}; };
h_flex() h_flex()
@ -412,7 +498,7 @@ impl AssistantPanel {
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.gap(DynamicSpacing::Base02.rems(cx)) .gap(DynamicSpacing::Base02.rems(cx))
.child( .child(
PopoverMenu::new("assistant-toolbar-popover-menu") PopoverMenu::new("assistant-toolbar-new-popover-menu")
.trigger( .trigger(
IconButton::new("new", IconName::Plus) IconButton::new("new", IconName::Plus)
.icon_size(IconSize::Small) .icon_size(IconSize::Small)
@ -429,22 +515,23 @@ impl AssistantPanel {
}), }),
) )
.child( .child(
IconButton::new("open-history", IconName::HistoryRerun) PopoverMenu::new("assistant-toolbar-history-popover-menu")
.icon_size(IconSize::Small) .trigger(
.style(ButtonStyle::Subtle) IconButton::new("open-history", IconName::HistoryRerun)
.tooltip({ .icon_size(IconSize::Small)
let focus_handle = focus_handle.clone(); .style(ButtonStyle::Subtle)
move |cx| { .tooltip(|cx| Tooltip::text("History…", cx)),
Tooltip::for_action_in( )
"Open History", .anchor(Corner::TopRight)
&OpenHistory, .with_handle(self.open_history_context_menu_handle.clone())
&focus_handle, .menu(move |cx| {
cx, Some(ContextMenu::build(cx, |menu, _| {
) menu.action("Thread History", OpenHistory.boxed_clone())
} .action(
}) "Prompt Editor History",
.on_click(move |_event, cx| { OpenPromptEditorHistory.boxed_clone(),
cx.dispatch_action(OpenHistory.boxed_clone()); )
}))
}), }),
) )
.child( .child(
@ -704,8 +791,52 @@ impl Render for AssistantPanel {
.child(self.message_editor.clone()), .child(self.message_editor.clone()),
) )
.children(self.render_last_error(cx)), .children(self.render_last_error(cx)),
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
ActiveView::History => parent.child(self.history.clone()), ActiveView::History => parent.child(self.history.clone()),
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
}) })
} }
} }
struct ConcreteAssistantPanelDelegate;
impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
fn active_context_editor(
&self,
workspace: &mut Workspace,
cx: &mut ViewContext<Workspace>,
) -> Option<View<ContextEditor>> {
let panel = workspace.panel::<AssistantPanel>(cx)?;
panel.update(cx, |panel, _cx| panel.context_editor.clone())
}
fn open_saved_context(
&self,
workspace: &mut Workspace,
path: std::path::PathBuf,
cx: &mut ViewContext<Workspace>,
) -> Task<Result<()>> {
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
return Task::ready(Err(anyhow!("Assistant panel not found")));
};
panel.update(cx, |panel, cx| panel.open_saved_prompt_editor(path, cx))
}
fn open_remote_context(
&self,
_workspace: &mut Workspace,
_context_id: assistant_context_editor::ContextId,
_cx: &mut ViewContext<Workspace>,
) -> Task<Result<View<ContextEditor>>> {
Task::ready(Err(anyhow!("opening remote context not implemented")))
}
fn quote_selection(
&self,
_workspace: &mut Workspace,
_creases: Vec<(String, String)>,
_cx: &mut ViewContext<Workspace>,
) {
}
}