Allow to regenerate a summary of the assistant context (#14964)
Both manual and LLM-through ways are supported: https://github.com/user-attachments/assets/afb0d2b3-9a9b-4f78-a909-1e663e686323 Release Notes: - Improved assistant panel summarization usability
This commit is contained in:
parent
a0d687c24a
commit
a5cb66f0e1
4 changed files with 204 additions and 100 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -375,7 +375,6 @@ dependencies = [
|
||||||
"assets",
|
"assets",
|
||||||
"assistant_slash_command",
|
"assistant_slash_command",
|
||||||
"async-watch",
|
"async-watch",
|
||||||
"breadcrumbs",
|
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
"chrono",
|
"chrono",
|
||||||
"client",
|
"client",
|
||||||
|
|
|
@ -26,7 +26,6 @@ anyhow.workspace = true
|
||||||
assets.workspace = true
|
assets.workspace = true
|
||||||
assistant_slash_command.workspace = true
|
assistant_slash_command.workspace = true
|
||||||
async-watch.workspace = true
|
async-watch.workspace = true
|
||||||
breadcrumbs.workspace = true
|
|
||||||
cargo_toml.workspace = true
|
cargo_toml.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
|
|
|
@ -16,7 +16,6 @@ use crate::{
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
|
||||||
use breadcrumbs::Breadcrumbs;
|
|
||||||
use client::proto;
|
use client::proto;
|
||||||
use collections::{BTreeSet, HashMap, HashSet};
|
use collections::{BTreeSet, HashMap, HashSet};
|
||||||
use completion::CompletionProvider;
|
use completion::CompletionProvider;
|
||||||
|
@ -50,6 +49,7 @@ use project::{Project, ProjectLspAdapterDelegate};
|
||||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering},
|
||||||
fmt::Write,
|
fmt::Write,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
|
@ -58,7 +58,6 @@ use std::{
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
use theme::ThemeSettings;
|
|
||||||
use ui::{
|
use ui::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
utils::{format_distance_from_now, DateTimeType},
|
utils::{format_distance_from_now, DateTimeType},
|
||||||
|
@ -68,7 +67,7 @@ use ui::{
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
dock::{DockPosition, Panel, PanelEvent},
|
dock::{DockPosition, Panel, PanelEvent},
|
||||||
item::{self, BreadcrumbText, FollowableItem, Item, ItemHandle},
|
item::{self, FollowableItem, Item, ItemHandle},
|
||||||
notifications::NotifyTaskExt,
|
notifications::NotifyTaskExt,
|
||||||
pane::{self, SaveIntent},
|
pane::{self, SaveIntent},
|
||||||
searchable::{SearchEvent, SearchableItem},
|
searchable::{SearchEvent, SearchableItem},
|
||||||
|
@ -113,6 +112,7 @@ pub struct AssistantPanel {
|
||||||
subscriptions: Vec<Subscription>,
|
subscriptions: Vec<Subscription>,
|
||||||
authentication_prompt: Option<AnyView>,
|
authentication_prompt: Option<AnyView>,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
|
model_summary_editor: View<Editor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -300,6 +300,14 @@ impl AssistantPanel {
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||||
|
let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||||
|
let context_editor_toolbar = cx.new_view(|_| {
|
||||||
|
ContextEditorToolbarItem::new(
|
||||||
|
workspace,
|
||||||
|
model_selector_menu_handle.clone(),
|
||||||
|
model_summary_editor.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
let pane = cx.new_view(|cx| {
|
let pane = cx.new_view(|cx| {
|
||||||
let mut pane = Pane::new(
|
let mut pane = Pane::new(
|
||||||
workspace.weak_handle(),
|
workspace.weak_handle(),
|
||||||
|
@ -345,13 +353,7 @@ impl AssistantPanel {
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
});
|
});
|
||||||
pane.toolbar().update(cx, |toolbar, cx| {
|
pane.toolbar().update(cx, |toolbar, cx| {
|
||||||
toolbar.add_item(cx.new_view(|_| Breadcrumbs::new()), cx);
|
toolbar.add_item(context_editor_toolbar.clone(), cx);
|
||||||
toolbar.add_item(
|
|
||||||
cx.new_view(|_| {
|
|
||||||
ContextEditorToolbarItem::new(workspace, model_selector_menu_handle.clone())
|
|
||||||
}),
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
|
toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
|
||||||
});
|
});
|
||||||
pane
|
pane
|
||||||
|
@ -360,6 +362,8 @@ impl AssistantPanel {
|
||||||
let subscriptions = vec![
|
let subscriptions = vec![
|
||||||
cx.observe(&pane, |_, _, cx| cx.notify()),
|
cx.observe(&pane, |_, _, cx| cx.notify()),
|
||||||
cx.subscribe(&pane, Self::handle_pane_event),
|
cx.subscribe(&pane, Self::handle_pane_event),
|
||||||
|
cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
|
||||||
|
cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
|
||||||
cx.observe_global::<CompletionProvider>({
|
cx.observe_global::<CompletionProvider>({
|
||||||
let mut prev_settings_version = CompletionProvider::global(cx).settings_version();
|
let mut prev_settings_version = CompletionProvider::global(cx).settings_version();
|
||||||
move |this, cx| {
|
move |this, cx| {
|
||||||
|
@ -381,6 +385,7 @@ impl AssistantPanel {
|
||||||
subscriptions,
|
subscriptions,
|
||||||
authentication_prompt: None,
|
authentication_prompt: None,
|
||||||
model_selector_menu_handle,
|
model_selector_menu_handle,
|
||||||
|
model_summary_editor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,10 +395,19 @@ impl AssistantPanel {
|
||||||
event: &pane::Event,
|
event: &pane::Event,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
let update_model_summary = match event {
|
||||||
pane::Event::Remove => cx.emit(PanelEvent::Close),
|
pane::Event::Remove => {
|
||||||
pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
|
cx.emit(PanelEvent::Close);
|
||||||
pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
|
false
|
||||||
|
}
|
||||||
|
pane::Event::ZoomIn => {
|
||||||
|
cx.emit(PanelEvent::ZoomIn);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
pane::Event::ZoomOut => {
|
||||||
|
cx.emit(PanelEvent::ZoomOut);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pane::Event::AddItem { item } => {
|
pane::Event::AddItem { item } => {
|
||||||
self.workspace
|
self.workspace
|
||||||
|
@ -401,6 +415,7 @@ impl AssistantPanel {
|
||||||
item.added_to_pane(workspace, self.pane.clone(), cx)
|
item.added_to_pane(workspace, self.pane.clone(), cx)
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pane::Event::ActivateItem { local } => {
|
pane::Event::ActivateItem { local } => {
|
||||||
|
@ -412,13 +427,59 @@ impl AssistantPanel {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
cx.emit(AssistantPanelEvent::ContextEdited);
|
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
pane::Event::RemoveItem { .. } => {
|
pane::Event::RemoveItem { .. } => {
|
||||||
cx.emit(AssistantPanelEvent::ContextEdited);
|
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if update_model_summary {
|
||||||
|
if let Some(editor) = self.active_context_editor(cx) {
|
||||||
|
self.show_updated_summary(&editor, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_summary_editor_event(
|
||||||
|
&mut self,
|
||||||
|
model_summary_editor: View<Editor>,
|
||||||
|
event: &EditorEvent,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if matches!(event, EditorEvent::Edited { .. }) {
|
||||||
|
if let Some(context_editor) = self.active_context_editor(cx) {
|
||||||
|
let new_summary = model_summary_editor.read(cx).text(cx);
|
||||||
|
context_editor.update(cx, |context_editor, cx| {
|
||||||
|
context_editor.context.update(cx, |context, cx| {
|
||||||
|
if context.summary().is_none()
|
||||||
|
&& (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.custom_summary(new_summary, cx)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_toolbar_event(
|
||||||
|
&mut self,
|
||||||
|
_: View<ContextEditorToolbarItem>,
|
||||||
|
_: &ContextEditorToolbarItemEvent,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(context_editor) = self.active_context_editor(cx) {
|
||||||
|
context_editor.update(cx, |context_editor, cx| {
|
||||||
|
context_editor.context.update(cx, |context, cx| {
|
||||||
|
context.summarize(true, cx);
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,17 +491,19 @@ impl AssistantPanel {
|
||||||
if self.is_authenticated(cx) {
|
if self.is_authenticated(cx) {
|
||||||
self.authentication_prompt = None;
|
self.authentication_prompt = None;
|
||||||
|
|
||||||
if let Some(editor) = self.active_context_editor(cx) {
|
match self.active_context_editor(cx) {
|
||||||
|
Some(editor) => {
|
||||||
editor.update(cx, |active_context, cx| {
|
editor.update(cx, |active_context, cx| {
|
||||||
active_context
|
active_context
|
||||||
.context
|
.context
|
||||||
.update(cx, |context, cx| context.completion_provider_changed(cx))
|
.update(cx, |context, cx| context.completion_provider_changed(cx))
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
if self.active_context_editor(cx).is_none() {
|
|
||||||
self.new_context(cx);
|
self.new_context(cx);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
} else if self.authentication_prompt.is_none()
|
} else if self.authentication_prompt.is_none()
|
||||||
|| prev_settings_version != CompletionProvider::global(cx).settings_version()
|
|| prev_settings_version != CompletionProvider::global(cx).settings_version()
|
||||||
|
@ -637,18 +700,43 @@ impl AssistantPanel {
|
||||||
.push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
|
.push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.show_updated_summary(&context_editor, cx);
|
||||||
|
|
||||||
cx.emit(AssistantPanelEvent::ContextEdited);
|
cx.emit(AssistantPanelEvent::ContextEdited);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_updated_summary(
|
||||||
|
&self,
|
||||||
|
context_editor: &View<ContextEditor>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
context_editor.update(cx, |context_editor, cx| {
|
||||||
|
let new_summary = context_editor
|
||||||
|
.context
|
||||||
|
.read(cx)
|
||||||
|
.summary()
|
||||||
|
.map(|s| s.text.clone())
|
||||||
|
.unwrap_or_else(|| context_editor.title(cx).to_string());
|
||||||
|
self.model_summary_editor.update(cx, |summary_editor, cx| {
|
||||||
|
if summary_editor.text(cx) != new_summary {
|
||||||
|
summary_editor.set_text(new_summary, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_context_editor_event(
|
fn handle_context_editor_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: View<ContextEditor>,
|
context_editor: View<ContextEditor>,
|
||||||
event: &EditorEvent,
|
event: &EditorEvent,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
) {
|
) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::TitleChanged { .. } => cx.notify(),
|
EditorEvent::TitleChanged => {
|
||||||
|
self.show_updated_summary(&context_editor, cx);
|
||||||
|
cx.notify()
|
||||||
|
}
|
||||||
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -1001,9 +1089,10 @@ pub struct ContextEditor {
|
||||||
assistant_panel: WeakView<AssistantPanel>,
|
assistant_panel: WeakView<AssistantPanel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextEditor {
|
const DEFAULT_TAB_TITLE: &str = "New Context";
|
||||||
const MAX_TAB_TITLE_LEN: usize = 16;
|
const MAX_TAB_TITLE_LEN: usize = 16;
|
||||||
|
|
||||||
|
impl ContextEditor {
|
||||||
fn for_context(
|
fn for_context(
|
||||||
context: Model<Context>,
|
context: Model<Context>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -1316,7 +1405,7 @@ impl ContextEditor {
|
||||||
ContextEvent::SummaryChanged => {
|
ContextEvent::SummaryChanged => {
|
||||||
cx.emit(EditorEvent::TitleChanged);
|
cx.emit(EditorEvent::TitleChanged);
|
||||||
self.context.update(cx, |context, cx| {
|
self.context.update(cx, |context, cx| {
|
||||||
context.save(None, self.fs.clone(), cx);
|
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ContextEvent::StreamedCompletion => {
|
ContextEvent::StreamedCompletion => {
|
||||||
|
@ -2031,16 +2120,18 @@ impl ContextEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
|
fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
|
||||||
self.context
|
self.context.update(cx, |context, cx| {
|
||||||
.update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
|
context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title(&self, cx: &AppContext) -> String {
|
fn title(&self, cx: &AppContext) -> Cow<str> {
|
||||||
self.context
|
self.context
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.summary()
|
.summary()
|
||||||
.map(|summary| summary.text.clone())
|
.map(|summary| summary.text.clone())
|
||||||
.unwrap_or_else(|| "New Context".into())
|
.map(Cow::Owned)
|
||||||
|
.unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
@ -2139,14 +2230,13 @@ impl Item for ContextEditor {
|
||||||
type Event = editor::EditorEvent;
|
type Event = editor::EditorEvent;
|
||||||
|
|
||||||
fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
|
fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
|
||||||
Some(util::truncate_and_trailoff(&self.title(cx), Self::MAX_TAB_TITLE_LEN).into())
|
Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
|
||||||
match event {
|
match event {
|
||||||
EditorEvent::Edited { .. } => {
|
EditorEvent::Edited { .. } => {
|
||||||
f(item::ItemEvent::Edit);
|
f(item::ItemEvent::Edit);
|
||||||
f(item::ItemEvent::UpdateBreadcrumbs);
|
|
||||||
}
|
}
|
||||||
EditorEvent::TitleChanged => {
|
EditorEvent::TitleChanged => {
|
||||||
f(item::ItemEvent::UpdateTab);
|
f(item::ItemEvent::UpdateTab);
|
||||||
|
@ -2156,48 +2246,13 @@ impl Item for ContextEditor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
|
||||||
Some(self.title(cx).into())
|
Some(self.title(cx).to_string().into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||||
Some(Box::new(handle.clone()))
|
Some(Box::new(handle.clone()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn breadcrumbs(
|
|
||||||
&self,
|
|
||||||
theme: &theme::Theme,
|
|
||||||
cx: &AppContext,
|
|
||||||
) -> Option<Vec<item::BreadcrumbText>> {
|
|
||||||
let editor = self.editor.read(cx);
|
|
||||||
let cursor = editor.selections.newest_anchor().head();
|
|
||||||
let multibuffer = &editor.buffer().read(cx);
|
|
||||||
let (_, symbols) = multibuffer.symbols_containing(cursor, Some(&theme.syntax()), cx)?;
|
|
||||||
|
|
||||||
let settings = ThemeSettings::get_global(cx);
|
|
||||||
|
|
||||||
let mut breadcrumbs = Vec::new();
|
|
||||||
|
|
||||||
let title = self.title(cx);
|
|
||||||
if title.chars().count() > Self::MAX_TAB_TITLE_LEN {
|
|
||||||
breadcrumbs.push(BreadcrumbText {
|
|
||||||
text: title,
|
|
||||||
highlights: None,
|
|
||||||
font: Some(settings.buffer_font.clone()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
|
|
||||||
text: symbol.text,
|
|
||||||
highlights: Some(symbol.highlight_ranges),
|
|
||||||
font: Some(settings.buffer_font.clone()),
|
|
||||||
}));
|
|
||||||
Some(breadcrumbs)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn breadcrumb_location(&self) -> ToolbarItemLocation {
|
|
||||||
ToolbarItemLocation::PrimaryLeft
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
|
fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
|
||||||
self.editor.update(cx, |editor, cx| {
|
self.editor.update(cx, |editor, cx| {
|
||||||
Item::set_nav_history(editor, nav_history, cx)
|
Item::set_nav_history(editor, nav_history, cx)
|
||||||
|
@ -2405,18 +2460,21 @@ pub struct ContextEditorToolbarItem {
|
||||||
workspace: WeakView<Workspace>,
|
workspace: WeakView<Workspace>,
|
||||||
active_context_editor: Option<WeakView<ContextEditor>>,
|
active_context_editor: Option<WeakView<ContextEditor>>,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
|
model_summary_editor: View<Editor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContextEditorToolbarItem {
|
impl ContextEditorToolbarItem {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
workspace: &Workspace,
|
workspace: &Workspace,
|
||||||
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
|
model_summary_editor: View<Editor>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
workspace: workspace.weak_handle(),
|
workspace: workspace.weak_handle(),
|
||||||
active_context_editor: None,
|
active_context_editor: None,
|
||||||
model_selector_menu_handle,
|
model_selector_menu_handle,
|
||||||
|
model_summary_editor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2524,14 +2582,35 @@ impl ContextEditorToolbarItem {
|
||||||
|
|
||||||
impl Render for ContextEditorToolbarItem {
|
impl Render for ContextEditorToolbarItem {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
h_flex()
|
let left_side = h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.flex_1()
|
||||||
|
.min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
|
||||||
|
.when(self.active_context_editor.is_some(), |left_side| {
|
||||||
|
left_side
|
||||||
|
.child(
|
||||||
|
IconButton::new("regenerate-context", IconName::ArrowCircle)
|
||||||
|
.tooltip(|cx| Tooltip::text("Regenerate Summary", cx))
|
||||||
|
.on_click(cx.listener(move |_, _, cx| {
|
||||||
|
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.child(self.model_summary_editor.clone())
|
||||||
|
});
|
||||||
|
let right_side = h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(ModelSelector::new(
|
.child(ModelSelector::new(
|
||||||
self.model_selector_menu_handle.clone(),
|
self.model_selector_menu_handle.clone(),
|
||||||
self.fs.clone(),
|
self.fs.clone(),
|
||||||
))
|
))
|
||||||
.children(self.render_remaining_tokens(cx))
|
.children(self.render_remaining_tokens(cx))
|
||||||
.child(self.render_inject_context_menu(cx))
|
.child(self.render_inject_context_menu(cx));
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.size_full()
|
||||||
|
.justify_between()
|
||||||
|
.child(left_side)
|
||||||
|
.child(right_side)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2559,6 +2638,11 @@ impl ToolbarItemView for ContextEditorToolbarItem {
|
||||||
|
|
||||||
impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
|
impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||||
|
|
||||||
|
enum ContextEditorToolbarItemEvent {
|
||||||
|
RegenerateSummary,
|
||||||
|
}
|
||||||
|
impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
|
||||||
|
|
||||||
pub struct ContextHistory {
|
pub struct ContextHistory {
|
||||||
picker: View<Picker<SavedContextPickerDelegate>>,
|
picker: View<Picker<SavedContextPickerDelegate>>,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
|
|
@ -9,7 +9,7 @@ use assistant_slash_command::{
|
||||||
use client::{self, proto, telemetry::Telemetry};
|
use client::{self, proto, telemetry::Telemetry};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fs::Fs;
|
use fs::{Fs, RemoveOptions};
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{self, Shared},
|
future::{self, Shared},
|
||||||
FutureExt, StreamExt,
|
FutureExt, StreamExt,
|
||||||
|
@ -1675,7 +1675,7 @@ impl Context {
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
this.pending_completions
|
this.pending_completions
|
||||||
.retain(|completion| completion.id != this.completion_count);
|
.retain(|completion| completion.id != this.completion_count);
|
||||||
this.summarize(cx);
|
this.summarize(false, cx);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
|
@ -1968,8 +1968,8 @@ impl Context {
|
||||||
self.message_anchors.insert(insertion_ix, new_anchor);
|
self.message_anchors.insert(insertion_ix, new_anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn summarize(&mut self, cx: &mut ModelContext<Self>) {
|
pub(super) fn summarize(&mut self, replace_old: bool, cx: &mut ModelContext<Self>) {
|
||||||
if self.message_anchors.len() >= 2 && self.summary.is_none() {
|
if replace_old || (self.message_anchors.len() >= 2 && self.summary.is_none()) {
|
||||||
if !CompletionProvider::global(cx).is_authenticated() {
|
if !CompletionProvider::global(cx).is_authenticated() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1993,13 +1993,18 @@ impl Context {
|
||||||
async move {
|
async move {
|
||||||
let mut messages = stream.await?;
|
let mut messages = stream.await?;
|
||||||
|
|
||||||
|
let mut replaced = !replace_old;
|
||||||
while let Some(message) = messages.next().await {
|
while let Some(message) = messages.next().await {
|
||||||
let text = message?;
|
let text = message?;
|
||||||
let mut lines = text.lines();
|
let mut lines = text.lines();
|
||||||
this.update(&mut cx, |this, cx| {
|
this.update(&mut cx, |this, cx| {
|
||||||
let version = this.version.clone();
|
let version = this.version.clone();
|
||||||
let timestamp = this.next_timestamp();
|
let timestamp = this.next_timestamp();
|
||||||
let summary = this.summary.get_or_insert(Default::default());
|
let summary = this.summary.get_or_insert(ContextSummary::default());
|
||||||
|
if !replaced && replace_old {
|
||||||
|
summary.text.clear();
|
||||||
|
replaced = true;
|
||||||
|
}
|
||||||
summary.text.extend(lines.next());
|
summary.text.extend(lines.next());
|
||||||
summary.timestamp = timestamp;
|
summary.timestamp = timestamp;
|
||||||
let operation = ContextOperation::UpdateSummary {
|
let operation = ContextOperation::UpdateSummary {
|
||||||
|
@ -2142,9 +2147,6 @@ impl Context {
|
||||||
|
|
||||||
if let Some(summary) = summary {
|
if let Some(summary) = summary {
|
||||||
let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
|
let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
|
||||||
let path = if let Some(old_path) = old_path {
|
|
||||||
old_path
|
|
||||||
} else {
|
|
||||||
let mut discriminant = 1;
|
let mut discriminant = 1;
|
||||||
let mut new_path;
|
let mut new_path;
|
||||||
loop {
|
loop {
|
||||||
|
@ -2159,18 +2161,38 @@ impl Context {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new_path
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.create_dir(contexts_dir().as_ref()).await?;
|
fs.create_dir(contexts_dir().as_ref()).await?;
|
||||||
fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
|
fs.atomic_write(new_path.clone(), serde_json::to_string(&context).unwrap())
|
||||||
.await?;
|
.await?;
|
||||||
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
if let Some(old_path) = old_path {
|
||||||
|
if new_path != old_path {
|
||||||
|
fs.remove_file(
|
||||||
|
&old_path,
|
||||||
|
RemoveOptions {
|
||||||
|
recursive: false,
|
||||||
|
ignore_if_not_exists: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.update(&mut cx, |this, _| this.path = Some(new_path))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn custom_summary(&mut self, custom_summary: String, cx: &mut ModelContext<Self>) {
|
||||||
|
let timestamp = self.next_timestamp();
|
||||||
|
let summary = self.summary.get_or_insert(ContextSummary::default());
|
||||||
|
summary.timestamp = timestamp;
|
||||||
|
summary.done = true;
|
||||||
|
summary.text = custom_summary;
|
||||||
|
cx.emit(ContextEvent::SummaryChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue