assistant2: Add thread history (#21599)
This PR adds support for thread history to the Assistant 2 panel. We also now generate summaries for the threads. <img width="986" alt="Screenshot 2024-12-05 at 12 56 53 PM" src="https://github.com/user-attachments/assets/46cb1309-38a2-4ab9-9fcc-c1275d4b5f2c"> <img width="986" alt="Screenshot 2024-12-05 at 12 56 58 PM" src="https://github.com/user-attachments/assets/8c91ba57-a6c5-4b88-be05-b22fb615ece5"> Release Notes: - N/A --------- Co-authored-by: Piotr <piotr@zed.dev>
This commit is contained in:
parent
2d43ad12e6
commit
787c75cbda
8 changed files with 375 additions and 127 deletions
|
@ -11,13 +11,15 @@ use gpui::{
|
|||
use language::LanguageRegistry;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, KeyBinding, ListItem, Tab, Tooltip};
|
||||
use time::UtcOffset;
|
||||
use ui::{prelude::*, ButtonLike, Divider, IconButtonShape, KeyBinding, Tab, Tooltip};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::active_thread::ActiveThread;
|
||||
use crate::message_editor::MessageEditor;
|
||||
use crate::thread::{Thread, ThreadError, ThreadId};
|
||||
use crate::thread::{ThreadError, ThreadId};
|
||||
use crate::thread_history::{PastThread, ThreadHistory};
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{NewThread, OpenHistory, ToggleFocus, ToggleModelSelector};
|
||||
|
||||
|
@ -32,13 +34,21 @@ pub fn init(cx: &mut AppContext) {
|
|||
.detach();
|
||||
}
|
||||
|
||||
enum ActiveView {
|
||||
Thread,
|
||||
History,
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
workspace: WeakView<Workspace>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
thread_store: Model<ThreadStore>,
|
||||
thread: Option<View<ActiveThread>>,
|
||||
thread: View<ActiveThread>,
|
||||
message_editor: View<MessageEditor>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
local_timezone: UtcOffset,
|
||||
active_view: ActiveView,
|
||||
history: View<ThreadHistory>,
|
||||
}
|
||||
|
||||
impl AssistantPanel {
|
||||
|
@ -68,14 +78,31 @@ impl AssistantPanel {
|
|||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
|
||||
let language_registry = workspace.project().read(cx).languages().clone();
|
||||
let workspace = workspace.weak_handle();
|
||||
let weak_self = cx.view().downgrade();
|
||||
|
||||
Self {
|
||||
workspace: workspace.weak_handle(),
|
||||
language_registry: workspace.project().read(cx).languages().clone(),
|
||||
thread_store,
|
||||
thread: None,
|
||||
message_editor: cx.new_view(|cx| MessageEditor::new(thread, cx)),
|
||||
active_view: ActiveView::Thread,
|
||||
workspace: workspace.clone(),
|
||||
language_registry: language_registry.clone(),
|
||||
thread_store: thread_store.clone(),
|
||||
thread: cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
workspace,
|
||||
language_registry,
|
||||
tools.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
message_editor: cx.new_view(|cx| MessageEditor::new(thread.clone(), cx)),
|
||||
tools,
|
||||
local_timezone: UtcOffset::from_whole_seconds(
|
||||
chrono::Local::now().offset().local_minus_utc(),
|
||||
)
|
||||
.unwrap(),
|
||||
history: cx.new_view(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +111,8 @@ impl AssistantPanel {
|
|||
.thread_store
|
||||
.update(cx, |this, cx| this.create_thread(cx));
|
||||
|
||||
self.thread = Some(cx.new_view(|cx| {
|
||||
self.active_view = ActiveView::Thread;
|
||||
self.thread = cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
self.workspace.clone(),
|
||||
|
@ -92,12 +120,12 @@ impl AssistantPanel {
|
|||
self.tools.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
});
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
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
|
||||
.thread_store
|
||||
.update(cx, |this, cx| this.open_thread(thread_id, cx))
|
||||
|
@ -105,7 +133,8 @@ impl AssistantPanel {
|
|||
return;
|
||||
};
|
||||
|
||||
self.thread = Some(cx.new_view(|cx| {
|
||||
self.active_view = ActiveView::Thread;
|
||||
self.thread = cx.new_view(|cx| {
|
||||
ActiveThread::new(
|
||||
thread.clone(),
|
||||
self.workspace.clone(),
|
||||
|
@ -113,15 +142,22 @@ impl AssistantPanel {
|
|||
self.tools.clone(),
|
||||
cx,
|
||||
)
|
||||
}));
|
||||
});
|
||||
self.message_editor = cx.new_view(|cx| MessageEditor::new(thread, cx));
|
||||
self.message_editor.focus_handle(cx).focus(cx);
|
||||
}
|
||||
|
||||
pub(crate) fn local_timezone(&self) -> UtcOffset {
|
||||
self.local_timezone
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for AssistantPanel {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
self.message_editor.focus_handle(cx)
|
||||
match self.active_view {
|
||||
ActiveView::Thread => self.message_editor.focus_handle(cx),
|
||||
ActiveView::History => self.history.focus_handle(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,7 +216,7 @@ impl AssistantPanel {
|
|||
.bg(cx.theme().colors().tab_bar_background)
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(h_flex().child(Label::new("Thread Title Goes Here")))
|
||||
.child(h_flex().children(self.thread.read(cx).summary(cx).map(Label::new)))
|
||||
.child(
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
|
@ -291,15 +327,11 @@ impl AssistantPanel {
|
|||
}
|
||||
|
||||
fn render_active_thread_or_empty_state(&self, cx: &mut ViewContext<Self>) -> AnyElement {
|
||||
let Some(thread) = self.thread.as_ref() else {
|
||||
return self.render_thread_empty_state(cx).into_any_element();
|
||||
};
|
||||
|
||||
if thread.read(cx).is_empty() {
|
||||
if self.thread.read(cx).is_empty() {
|
||||
return self.render_thread_empty_state(cx).into_any_element();
|
||||
}
|
||||
|
||||
thread.clone().into_any()
|
||||
self.thread.clone().into_any()
|
||||
}
|
||||
|
||||
fn render_thread_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
|
@ -361,63 +393,41 @@ impl AssistantPanel {
|
|||
.child(Label::new("/src/components").size(LabelSize::Small)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_center()
|
||||
.child(Label::new("Recent Threads:").size(LabelSize::Small)),
|
||||
)
|
||||
.child(
|
||||
v_flex().gap_2().children(
|
||||
recent_threads
|
||||
.into_iter()
|
||||
.map(|thread| self.render_past_thread(thread, cx)),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("view-all-past-threads", "View All Past Threads")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenHistory.boxed_clone());
|
||||
}),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_past_thread(
|
||||
&self,
|
||||
thread: Model<Thread>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let id = thread.read(cx).id().clone();
|
||||
|
||||
ListItem::new(("past-thread", thread.entity_id()))
|
||||
.start_slot(Icon::new(IconName::MessageBubbles))
|
||||
.child(Label::new(format!("Thread {id}")))
|
||||
.end_slot(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Label::new("1 hour ago").color(Color::Disabled))
|
||||
.when(!recent_threads.is_empty(), |parent| {
|
||||
parent
|
||||
.child(
|
||||
IconButton::new("delete", IconName::TrashAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small),
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _event, cx| {
|
||||
this.open_thread(&id, cx);
|
||||
}))
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_center()
|
||||
.child(Label::new("Recent Threads:").size(LabelSize::Small)),
|
||||
)
|
||||
.child(
|
||||
v_flex().gap_2().children(
|
||||
recent_threads
|
||||
.into_iter()
|
||||
.map(|thread| PastThread::new(thread, cx.view().downgrade())),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().w_full().justify_center().child(
|
||||
Button::new("view-all-past-threads", "View All Past Threads")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.key_binding(KeyBinding::for_action_in(
|
||||
&OpenHistory,
|
||||
&self.focus_handle(cx),
|
||||
cx,
|
||||
))
|
||||
.on_click(move |_event, cx| {
|
||||
cx.dispatch_action(OpenHistory.boxed_clone());
|
||||
}),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
let last_error = self.thread.as_ref()?.read(cx).last_error()?;
|
||||
let last_error = self.thread.read(cx).last_error()?;
|
||||
|
||||
Some(
|
||||
div()
|
||||
|
@ -467,11 +477,9 @@ impl AssistantPanel {
|
|||
.mt_1()
|
||||
.child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
if let Some(thread) = this.thread.as_ref() {
|
||||
thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
}
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.open_url(&zed_urls::account_url(cx));
|
||||
cx.notify();
|
||||
|
@ -479,11 +487,9 @@ impl AssistantPanel {
|
|||
)))
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
if let Some(thread) = this.thread.as_ref() {
|
||||
thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
}
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
},
|
||||
|
@ -518,11 +524,9 @@ impl AssistantPanel {
|
|||
.child(
|
||||
Button::new("subscribe", "Update Monthly Spend Limit").on_click(
|
||||
cx.listener(|this, _, cx| {
|
||||
if let Some(thread) = this.thread.as_ref() {
|
||||
thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
}
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.open_url(&zed_urls::account_url(cx));
|
||||
cx.notify();
|
||||
|
@ -531,11 +535,9 @@ impl AssistantPanel {
|
|||
)
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
if let Some(thread) = this.thread.as_ref() {
|
||||
thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
}
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
},
|
||||
|
@ -574,11 +576,9 @@ impl AssistantPanel {
|
|||
.mt_1()
|
||||
.child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
if let Some(thread) = this.thread.as_ref() {
|
||||
thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
}
|
||||
this.thread.update(cx, |this, _cx| {
|
||||
this.clear_last_error();
|
||||
});
|
||||
|
||||
cx.notify();
|
||||
},
|
||||
|
@ -597,17 +597,23 @@ impl Render for AssistantPanel {
|
|||
.on_action(cx.listener(|this, _: &NewThread, cx| {
|
||||
this.new_thread(cx);
|
||||
}))
|
||||
.on_action(cx.listener(|_this, _: &OpenHistory, _cx| {
|
||||
println!("Open History");
|
||||
.on_action(cx.listener(|this, _: &OpenHistory, cx| {
|
||||
this.active_view = ActiveView::History;
|
||||
this.history.focus_handle(cx).focus(cx);
|
||||
cx.notify();
|
||||
}))
|
||||
.child(self.render_toolbar(cx))
|
||||
.child(self.render_active_thread_or_empty_state(cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
.children(self.render_last_error(cx))
|
||||
.map(|parent| match self.active_view {
|
||||
ActiveView::Thread => parent
|
||||
.child(self.render_active_thread_or_empty_state(cx))
|
||||
.child(
|
||||
h_flex()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.child(self.message_editor.clone()),
|
||||
)
|
||||
.children(self.render_last_error(cx)),
|
||||
ActiveView::History => parent.child(self.history.clone()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue