assistant2: Combine history views into one (#25293)
This PR combines the two history views in Assistant2 into one. <img width="1309" alt="Screenshot 2025-02-20 at 5 34 37 PM" src="https://github.com/user-attachments/assets/fbb08542-58b5-4930-8a20-254234e335fa" /> <img width="1309" alt="Screenshot 2025-02-20 at 5 34 41 PM" src="https://github.com/user-attachments/assets/1174849e-edad-4e02-8bf3-bb92aafba4f8" /> Release Notes: - N/A
This commit is contained in:
parent
3a222f0666
commit
74c581b9f4
6 changed files with 265 additions and 155 deletions
|
@ -7,6 +7,7 @@ mod context;
|
||||||
mod context_picker;
|
mod context_picker;
|
||||||
mod context_store;
|
mod context_store;
|
||||||
mod context_strip;
|
mod context_strip;
|
||||||
|
mod history_store;
|
||||||
mod inline_assistant;
|
mod inline_assistant;
|
||||||
mod inline_prompt_editor;
|
mod inline_prompt_editor;
|
||||||
mod message_editor;
|
mod message_editor;
|
||||||
|
@ -40,7 +41,6 @@ actions!(
|
||||||
ToggleModelSelector,
|
ToggleModelSelector,
|
||||||
RemoveAllContext,
|
RemoveAllContext,
|
||||||
OpenHistory,
|
OpenHistory,
|
||||||
OpenPromptEditorHistory,
|
|
||||||
OpenConfiguration,
|
OpenConfiguration,
|
||||||
RemoveSelectedThread,
|
RemoveSelectedThread,
|
||||||
Chat,
|
Chat,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::sync::Arc;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_context_editor::{
|
use assistant_context_editor::{
|
||||||
make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError,
|
make_lsp_adapter_delegate, render_remaining_tokens, AssistantPanelDelegate, ConfigurationError,
|
||||||
ContextEditor, ContextHistory, SlashCommandCompletionProvider,
|
ContextEditor, SlashCommandCompletionProvider,
|
||||||
};
|
};
|
||||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||||
use assistant_slash_command::SlashCommandWorkingSet;
|
use assistant_slash_command::SlashCommandWorkingSet;
|
||||||
|
@ -31,14 +31,12 @@ use zed_actions::assistant::{DeployPromptLibrary, ToggleFocus};
|
||||||
|
|
||||||
use crate::active_thread::ActiveThread;
|
use crate::active_thread::ActiveThread;
|
||||||
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
|
||||||
|
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||||
use crate::message_editor::MessageEditor;
|
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::{PastContext, PastThread, ThreadHistory};
|
||||||
use crate::thread_store::ThreadStore;
|
use crate::thread_store::ThreadStore;
|
||||||
use crate::{
|
use crate::{InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory};
|
||||||
InlineAssistant, NewPromptEditor, NewThread, OpenConfiguration, OpenHistory,
|
|
||||||
OpenPromptEditorHistory,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(cx: &mut App) {
|
pub fn init(cx: &mut App) {
|
||||||
cx.observe_new(
|
cx.observe_new(
|
||||||
|
@ -62,12 +60,6 @@ pub fn init(cx: &mut App) {
|
||||||
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.register_action(|workspace, _: &OpenPromptEditorHistory, window, cx| {
|
|
||||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
|
||||||
panel.update(cx, |panel, cx| panel.open_prompt_editor_history(window, cx));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
.register_action(|workspace, _: &OpenConfiguration, window, cx| {
|
||||||
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
workspace.focus_panel::<AssistantPanel>(window, cx);
|
workspace.focus_panel::<AssistantPanel>(window, cx);
|
||||||
|
@ -83,7 +75,6 @@ enum ActiveView {
|
||||||
Thread,
|
Thread,
|
||||||
PromptEditor,
|
PromptEditor,
|
||||||
History,
|
History,
|
||||||
PromptEditorHistory,
|
|
||||||
Configuration,
|
Configuration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,15 +88,14 @@ pub struct AssistantPanel {
|
||||||
message_editor: Entity<MessageEditor>,
|
message_editor: Entity<MessageEditor>,
|
||||||
context_store: Entity<assistant_context_editor::ContextStore>,
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
context_editor: Option<Entity<ContextEditor>>,
|
context_editor: Option<Entity<ContextEditor>>,
|
||||||
context_history: Option<Entity<ContextHistory>>,
|
|
||||||
configuration: Option<Entity<AssistantConfiguration>>,
|
configuration: Option<Entity<AssistantConfiguration>>,
|
||||||
configuration_subscription: Option<Subscription>,
|
configuration_subscription: Option<Subscription>,
|
||||||
tools: Arc<ToolWorkingSet>,
|
tools: Arc<ToolWorkingSet>,
|
||||||
local_timezone: UtcOffset,
|
local_timezone: UtcOffset,
|
||||||
active_view: ActiveView,
|
active_view: ActiveView,
|
||||||
|
history_store: Entity<HistoryStore>,
|
||||||
history: Entity<ThreadHistory>,
|
history: Entity<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>,
|
||||||
}
|
}
|
||||||
|
@ -173,6 +163,9 @@ impl AssistantPanel {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let history_store =
|
||||||
|
cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
active_view: ActiveView::Thread,
|
active_view: ActiveView::Thread,
|
||||||
workspace: workspace.clone(),
|
workspace: workspace.clone(),
|
||||||
|
@ -194,7 +187,6 @@ impl AssistantPanel {
|
||||||
message_editor,
|
message_editor,
|
||||||
context_store,
|
context_store,
|
||||||
context_editor: None,
|
context_editor: None,
|
||||||
context_history: None,
|
|
||||||
configuration: None,
|
configuration: None,
|
||||||
configuration_subscription: None,
|
configuration_subscription: None,
|
||||||
tools,
|
tools,
|
||||||
|
@ -202,9 +194,9 @@ impl AssistantPanel {
|
||||||
chrono::Local::now().offset().local_minus_utc(),
|
chrono::Local::now().offset().local_minus_utc(),
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
history: cx.new(|cx| ThreadHistory::new(weak_self, thread_store, cx)),
|
history_store: history_store.clone(),
|
||||||
|
history: cx.new(|cx| ThreadHistory::new(weak_self, history_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,
|
||||||
}
|
}
|
||||||
|
@ -331,26 +323,7 @@ impl AssistantPanel {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_prompt_editor_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(crate) fn open_saved_prompt_editor(
|
||||||
self.active_view = ActiveView::PromptEditorHistory;
|
|
||||||
self.context_history = Some(cx.new(|cx| {
|
|
||||||
ContextHistory::new(
|
|
||||||
self.project.clone(),
|
|
||||||
self.context_store.clone(),
|
|
||||||
self.workspace.clone(),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
}));
|
|
||||||
|
|
||||||
if let Some(context_history) = self.context_history.as_ref() {
|
|
||||||
context_history.focus_handle(cx).focus(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_saved_prompt_editor(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
@ -499,13 +472,6 @@ impl Focusable for AssistantPanel {
|
||||||
cx.focus_handle()
|
cx.focus_handle()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveView::PromptEditorHistory => {
|
|
||||||
if let Some(context_history) = self.context_history.as_ref() {
|
|
||||||
context_history.focus_handle(cx)
|
|
||||||
} else {
|
|
||||||
cx.focus_handle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ActiveView::Configuration => {
|
ActiveView::Configuration => {
|
||||||
if let Some(configuration) = self.configuration.as_ref() {
|
if let Some(configuration) = self.configuration.as_ref() {
|
||||||
configuration.focus_handle(cx)
|
configuration.focus_handle(cx)
|
||||||
|
@ -618,18 +584,10 @@ 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 | ActiveView::PromptEditorHistory => "History".into(),
|
ActiveView::History => "History".into(),
|
||||||
ActiveView::Configuration => "Assistant Settings".into(),
|
ActiveView::Configuration => "Assistant Settings".into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let sub_title = match self.active_view {
|
|
||||||
ActiveView::Thread => None,
|
|
||||||
ActiveView::PromptEditor => None,
|
|
||||||
ActiveView::History => Some("Thread"),
|
|
||||||
ActiveView::PromptEditorHistory => Some("Prompt Editor"),
|
|
||||||
ActiveView::Configuration => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("assistant-toolbar")
|
.id("assistant-toolbar")
|
||||||
.px(DynamicSpacing::Base08.rems(cx))
|
.px(DynamicSpacing::Base08.rems(cx))
|
||||||
|
@ -645,24 +603,7 @@ impl AssistantPanel {
|
||||||
.w_full()
|
.w_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.child(Label::new(title))
|
.child(Label::new(title))
|
||||||
.when(sub_title.is_some(), |this| {
|
|
||||||
this.child(
|
|
||||||
h_flex()
|
|
||||||
.pl_1p5()
|
|
||||||
.gap_1p5()
|
|
||||||
.child(
|
|
||||||
Label::new("/")
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Disabled)
|
|
||||||
.alpha(0.5),
|
|
||||||
)
|
|
||||||
.child(Label::new(sub_title.unwrap())),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
|
.children(if matches!(self.active_view, ActiveView::PromptEditor) {
|
||||||
self.context_editor
|
self.context_editor
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -696,23 +637,23 @@ impl AssistantPanel {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
PopoverMenu::new("assistant-toolbar-history-popover-menu")
|
|
||||||
.trigger_with_tooltip(
|
|
||||||
IconButton::new("open-history", IconName::HistoryRerun)
|
IconButton::new("open-history", IconName::HistoryRerun)
|
||||||
.icon_size(IconSize::Small)
|
.icon_size(IconSize::Small)
|
||||||
.style(ButtonStyle::Subtle),
|
.style(ButtonStyle::Subtle)
|
||||||
Tooltip::text("History…"),
|
.tooltip({
|
||||||
|
let focus_handle = self.focus_handle(cx);
|
||||||
|
move |window, cx| {
|
||||||
|
Tooltip::for_action_in(
|
||||||
|
"History",
|
||||||
|
&OpenHistory,
|
||||||
|
&focus_handle,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
)
|
)
|
||||||
.anchor(Corner::TopRight)
|
}
|
||||||
.with_handle(self.open_history_context_menu_handle.clone())
|
})
|
||||||
.menu(move |window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
|
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||||
menu.action("Thread History", OpenHistory.boxed_clone())
|
|
||||||
.action(
|
|
||||||
"Prompt Editor History",
|
|
||||||
OpenPromptEditorHistory.boxed_clone(),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
@ -762,9 +703,9 @@ impl AssistantPanel {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let recent_threads = self
|
let recent_history = self
|
||||||
.thread_store
|
.history_store
|
||||||
.update(cx, |this, _cx| this.recent_threads(3));
|
.update(cx, |this, cx| this.recent_entries(3, cx));
|
||||||
|
|
||||||
let create_welcome_heading = || {
|
let create_welcome_heading = || {
|
||||||
h_flex()
|
h_flex()
|
||||||
|
@ -791,7 +732,8 @@ impl AssistantPanel {
|
||||||
)
|
)
|
||||||
.map(|parent| {
|
.map(|parent| {
|
||||||
match configuration_error {
|
match configuration_error {
|
||||||
Some(ConfigurationError::ProviderNotAuthenticated) | Some(ConfigurationError::NoProvider) => {
|
Some(ConfigurationError::ProviderNotAuthenticated)
|
||||||
|
| Some(ConfigurationError::NoProvider) => {
|
||||||
parent.child(
|
parent.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_0p5()
|
.gap_0p5()
|
||||||
|
@ -818,34 +760,24 @@ impl AssistantPanel {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
|
Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => parent
|
||||||
parent.child(
|
.child(v_flex().gap_0p5().child(create_welcome_heading()).children(
|
||||||
v_flex()
|
provider.render_accept_terms(
|
||||||
.gap_0p5()
|
|
||||||
.child(create_welcome_heading())
|
|
||||||
.children(provider.render_accept_terms(
|
|
||||||
LanguageModelProviderTosView::ThreadEmptyState,
|
LanguageModelProviderTosView::ThreadEmptyState,
|
||||||
cx,
|
cx,
|
||||||
|
),
|
||||||
)),
|
)),
|
||||||
)
|
|
||||||
}
|
|
||||||
None => parent,
|
None => parent,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when(
|
.when(recent_history.is_empty() && no_error, |parent| {
|
||||||
recent_threads.is_empty() && no_error,
|
parent.child(v_flex().gap_0p5().child(create_welcome_heading()).child(
|
||||||
|parent| {
|
|
||||||
parent.child(
|
|
||||||
v_flex().gap_0p5().child(create_welcome_heading()).child(
|
|
||||||
h_flex().w_full().justify_center().child(
|
h_flex().w_full().justify_center().child(
|
||||||
Label::new("Start typing to chat with your codebase")
|
Label::new("Start typing to chat with your codebase").color(Color::Muted),
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
),
|
||||||
),
|
))
|
||||||
)
|
})
|
||||||
},
|
.when(!recent_history.is_empty(), |parent| {
|
||||||
)
|
|
||||||
.when(!recent_threads.is_empty(), |parent| {
|
|
||||||
parent
|
parent
|
||||||
.child(
|
.child(
|
||||||
h_flex().w_full().justify_center().child(
|
h_flex().w_full().justify_center().child(
|
||||||
|
@ -855,9 +787,18 @@ impl AssistantPanel {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
.child(v_flex().mx_auto().w_4_5().gap_2().children(
|
||||||
recent_threads.into_iter().map(|thread| {
|
recent_history.into_iter().map(|entry| {
|
||||||
// TODO: keyboard navigation
|
// TODO: Add keyboard navigation.
|
||||||
|
match entry {
|
||||||
|
HistoryEntry::Thread(thread) => {
|
||||||
PastThread::new(thread, cx.entity().downgrade(), false)
|
PastThread::new(thread, cx.entity().downgrade(), false)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
HistoryEntry::Context(context) => {
|
||||||
|
PastContext::new(context, cx.entity().downgrade(), false)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
))
|
))
|
||||||
.child(
|
.child(
|
||||||
|
@ -869,7 +810,7 @@ impl AssistantPanel {
|
||||||
&OpenHistory,
|
&OpenHistory,
|
||||||
&self.focus_handle(cx),
|
&self.focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx
|
cx,
|
||||||
))
|
))
|
||||||
.on_click(move |_event, window, cx| {
|
.on_click(move |_event, window, cx| {
|
||||||
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
window.dispatch_action(OpenHistory.boxed_clone(), cx);
|
||||||
|
@ -1068,7 +1009,6 @@ impl Render for AssistantPanel {
|
||||||
.children(self.render_last_error(cx)),
|
.children(self.render_last_error(cx)),
|
||||||
ActiveView::History => parent.child(self.history.clone()),
|
ActiveView::History => parent.child(self.history.clone()),
|
||||||
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
|
ActiveView::PromptEditor => parent.children(self.context_editor.clone()),
|
||||||
ActiveView::PromptEditorHistory => parent.children(self.context_history.clone()),
|
|
||||||
ActiveView::Configuration => parent.children(self.configuration.clone()),
|
ActiveView::Configuration => parent.children(self.configuration.clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
61
crates/assistant2/src/history_store.rs
Normal file
61
crates/assistant2/src/history_store.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
use assistant_context_editor::SavedContextMetadata;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use gpui::{prelude::*, Entity};
|
||||||
|
|
||||||
|
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
||||||
|
|
||||||
|
pub enum HistoryEntry {
|
||||||
|
Thread(SavedThreadMetadata),
|
||||||
|
Context(SavedContextMetadata),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryEntry {
|
||||||
|
pub fn updated_at(&self) -> DateTime<Utc> {
|
||||||
|
match self {
|
||||||
|
HistoryEntry::Thread(thread) => thread.updated_at,
|
||||||
|
HistoryEntry::Context(context) => context.mtime.to_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HistoryStore {
|
||||||
|
thread_store: Entity<ThreadStore>,
|
||||||
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HistoryStore {
|
||||||
|
pub fn new(
|
||||||
|
thread_store: Entity<ThreadStore>,
|
||||||
|
context_store: Entity<assistant_context_editor::ContextStore>,
|
||||||
|
_cx: &mut Context<Self>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
thread_store,
|
||||||
|
context_store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of history entries.
|
||||||
|
pub fn entry_count(&self, cx: &mut Context<Self>) -> usize {
|
||||||
|
self.entries(cx).len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entries(&self, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||||
|
let mut history_entries = Vec::new();
|
||||||
|
|
||||||
|
for thread in self.thread_store.update(cx, |this, _cx| this.threads()) {
|
||||||
|
history_entries.push(HistoryEntry::Thread(thread));
|
||||||
|
}
|
||||||
|
|
||||||
|
for context in self.context_store.update(cx, |this, _cx| this.contexts()) {
|
||||||
|
history_entries.push(HistoryEntry::Context(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
history_entries.sort_unstable_by_key(|entry| std::cmp::Reverse(entry.updated_at()));
|
||||||
|
history_entries
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recent_entries(&self, limit: usize, cx: &mut Context<Self>) -> Vec<HistoryEntry> {
|
||||||
|
self.entries(cx).into_iter().take(limit).collect()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use assistant_context_editor::SavedContextMetadata;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
uniform_list, App, Entity, FocusHandle, Focusable, ScrollStrategy, UniformListScrollHandle,
|
||||||
WeakEntity,
|
WeakEntity,
|
||||||
|
@ -5,13 +6,14 @@ use gpui::{
|
||||||
use time::{OffsetDateTime, UtcOffset};
|
use time::{OffsetDateTime, UtcOffset};
|
||||||
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
use ui::{prelude::*, IconButtonShape, ListItem, ListItemSpacing, Tooltip};
|
||||||
|
|
||||||
use crate::thread_store::{SavedThreadMetadata, ThreadStore};
|
use crate::history_store::{HistoryEntry, HistoryStore};
|
||||||
|
use crate::thread_store::SavedThreadMetadata;
|
||||||
use crate::{AssistantPanel, RemoveSelectedThread};
|
use crate::{AssistantPanel, RemoveSelectedThread};
|
||||||
|
|
||||||
pub struct ThreadHistory {
|
pub struct ThreadHistory {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
|
@ -19,13 +21,13 @@ pub struct ThreadHistory {
|
||||||
impl ThreadHistory {
|
impl ThreadHistory {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
assistant_panel: WeakEntity<AssistantPanel>,
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
thread_store: Entity<ThreadStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
assistant_panel,
|
assistant_panel,
|
||||||
thread_store,
|
history_store,
|
||||||
scroll_handle: UniformListScrollHandle::default(),
|
scroll_handle: UniformListScrollHandle::default(),
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
}
|
}
|
||||||
|
@ -37,7 +39,9 @@ impl ThreadHistory {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
let count = self
|
||||||
|
.history_store
|
||||||
|
.update(cx, |this, cx| this.entry_count(cx));
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == 0 {
|
if self.selected_index == 0 {
|
||||||
self.set_selected_index(count - 1, window, cx);
|
self.set_selected_index(count - 1, window, cx);
|
||||||
|
@ -53,7 +57,9 @@ impl ThreadHistory {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
let count = self
|
||||||
|
.history_store
|
||||||
|
.update(cx, |this, cx| this.entry_count(cx));
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
if self.selected_index == count - 1 {
|
if self.selected_index == count - 1 {
|
||||||
self.set_selected_index(0, window, cx);
|
self.set_selected_index(0, window, cx);
|
||||||
|
@ -64,14 +70,18 @@ impl ThreadHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
let count = self
|
||||||
|
.history_store
|
||||||
|
.update(cx, |this, cx| this.entry_count(cx));
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
self.set_selected_index(0, window, cx);
|
self.set_selected_index(0, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let count = self.thread_store.read(cx).thread_count();
|
let count = self
|
||||||
|
.history_store
|
||||||
|
.update(cx, |this, cx| this.entry_count(cx));
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
self.set_selected_index(count - 1, window, cx);
|
self.set_selected_index(count - 1, window, cx);
|
||||||
}
|
}
|
||||||
|
@ -85,12 +95,23 @@ impl ThreadHistory {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||||
|
|
||||||
if let Some(thread) = threads.get(self.selected_index) {
|
if let Some(entry) = entries.get(self.selected_index) {
|
||||||
|
match entry {
|
||||||
|
HistoryEntry::Thread(thread) => {
|
||||||
self.assistant_panel
|
self.assistant_panel
|
||||||
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
.update(cx, move |this, cx| this.open_thread(&thread.id, window, cx))
|
||||||
.ok();
|
.ok();
|
||||||
|
}
|
||||||
|
HistoryEntry::Context(context) => {
|
||||||
|
self.assistant_panel
|
||||||
|
.update(cx, move |this, cx| {
|
||||||
|
this.open_saved_prompt_editor(context.path.clone(), window, cx)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -102,14 +123,19 @@ impl ThreadHistory {
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
let entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||||
|
|
||||||
if let Some(thread) = threads.get(self.selected_index) {
|
if let Some(entry) = entries.get(self.selected_index) {
|
||||||
|
match entry {
|
||||||
|
HistoryEntry::Thread(thread) => {
|
||||||
self.assistant_panel
|
self.assistant_panel
|
||||||
.update(cx, |this, cx| {
|
.update(cx, |this, cx| {
|
||||||
this.delete_thread(&thread.id, cx);
|
this.delete_thread(&thread.id, cx);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
}
|
||||||
|
HistoryEntry::Context(_context) => {}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -124,7 +150,7 @@ impl Focusable for ThreadHistory {
|
||||||
|
|
||||||
impl Render for ThreadHistory {
|
impl Render for ThreadHistory {
|
||||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let threads = self.thread_store.update(cx, |this, _cx| this.threads());
|
let history_entries = self.history_store.update(cx, |this, cx| this.entries(cx));
|
||||||
let selected_index = self.selected_index;
|
let selected_index = self.selected_index;
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
|
@ -141,7 +167,7 @@ impl Render for ThreadHistory {
|
||||||
.on_action(cx.listener(Self::confirm))
|
.on_action(cx.listener(Self::confirm))
|
||||||
.on_action(cx.listener(Self::remove_selected_thread))
|
.on_action(cx.listener(Self::remove_selected_thread))
|
||||||
.map(|history| {
|
.map(|history| {
|
||||||
if threads.is_empty() {
|
if history_entries.is_empty() {
|
||||||
history
|
history
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.child(
|
.child(
|
||||||
|
@ -155,17 +181,26 @@ impl Render for ThreadHistory {
|
||||||
uniform_list(
|
uniform_list(
|
||||||
cx.entity().clone(),
|
cx.entity().clone(),
|
||||||
"thread-history",
|
"thread-history",
|
||||||
threads.len(),
|
history_entries.len(),
|
||||||
move |history, range, _window, _cx| {
|
move |history, range, _window, _cx| {
|
||||||
threads[range]
|
history_entries[range]
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, thread)| {
|
.map(|(index, entry)| {
|
||||||
h_flex().w_full().pb_1().child(PastThread::new(
|
h_flex().w_full().pb_1().child(match entry {
|
||||||
|
HistoryEntry::Thread(thread) => PastThread::new(
|
||||||
thread.clone(),
|
thread.clone(),
|
||||||
history.assistant_panel.clone(),
|
history.assistant_panel.clone(),
|
||||||
selected_index == index,
|
selected_index == index,
|
||||||
))
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
HistoryEntry::Context(context) => PastContext::new(
|
||||||
|
context.clone(),
|
||||||
|
history.assistant_panel.clone(),
|
||||||
|
selected_index == index,
|
||||||
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
},
|
},
|
||||||
|
@ -261,3 +296,71 @@ impl RenderOnce for PastThread {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct PastContext {
|
||||||
|
context: SavedContextMetadata,
|
||||||
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PastContext {
|
||||||
|
pub fn new(
|
||||||
|
context: SavedContextMetadata,
|
||||||
|
assistant_panel: WeakEntity<AssistantPanel>,
|
||||||
|
selected: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
context,
|
||||||
|
assistant_panel,
|
||||||
|
selected,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for PastContext {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let summary = self.context.title;
|
||||||
|
|
||||||
|
let context_timestamp = time_format::format_localized_timestamp(
|
||||||
|
OffsetDateTime::from_unix_timestamp(self.context.mtime.timestamp()).unwrap(),
|
||||||
|
OffsetDateTime::now_utc(),
|
||||||
|
self.assistant_panel
|
||||||
|
.update(cx, |this, _cx| this.local_timezone())
|
||||||
|
.unwrap_or(UtcOffset::UTC),
|
||||||
|
time_format::TimestampFormat::EnhancedAbsolute,
|
||||||
|
);
|
||||||
|
|
||||||
|
ListItem::new(SharedString::from(
|
||||||
|
self.context.path.to_string_lossy().to_string(),
|
||||||
|
))
|
||||||
|
.outlined()
|
||||||
|
.toggle_state(self.selected)
|
||||||
|
.start_slot(
|
||||||
|
Icon::new(IconName::Code)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.child(Label::new(summary).size(LabelSize::Small).text_ellipsis())
|
||||||
|
.end_slot(
|
||||||
|
h_flex().gap_1p5().child(
|
||||||
|
Label::new(context_timestamp)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(LabelSize::XSmall),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.on_click({
|
||||||
|
let assistant_panel = self.assistant_panel.clone();
|
||||||
|
let path = self.context.path.clone();
|
||||||
|
move |_event, window, cx| {
|
||||||
|
assistant_panel
|
||||||
|
.update(cx, |this, cx| {
|
||||||
|
this.open_saved_prompt_editor(path.clone(), window, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3366,7 +3366,7 @@ impl SavedContextV0_1_0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SavedContextMetadata {
|
pub struct SavedContextMetadata {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub path: PathBuf,
|
pub path: PathBuf,
|
||||||
|
|
|
@ -350,6 +350,12 @@ impl ContextStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contexts(&self) -> Vec<SavedContextMetadata> {
|
||||||
|
let mut contexts = self.contexts_metadata.iter().cloned().collect::<Vec<_>>();
|
||||||
|
contexts.sort_unstable_by_key(|thread| std::cmp::Reverse(thread.mtime));
|
||||||
|
contexts
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
pub fn create(&mut self, cx: &mut Context<Self>) -> Entity<AssistantContext> {
|
||||||
let context = cx.new(|cx| {
|
let context = cx.new(|cx| {
|
||||||
AssistantContext::local(
|
AssistantContext::local(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue