Introduce recent files ambient context for assistant (#11791)

<img width="1637" alt="image"
src="https://github.com/zed-industries/zed/assets/482957/5aaec657-3499-42c9-9528-c83728f2a7a1">

Release Notes:

- Added a new ambient context feature that allows showing the model up
to three buffers (along with their diagnostics) that the user interacted
with recently.

---------

Co-authored-by: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Antonio Scandurra 2024-05-14 13:48:36 +02:00 committed by GitHub
parent e4c95b25bf
commit a13a92fbbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 522 additions and 411 deletions

View file

@ -0,0 +1 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M13.15 7.49998C13.15 4.66458 10.9402 1.84998 7.50002 1.84998C4.7217 1.84998 3.34851 3.90636 2.76336 4.99997H4.5C4.77614 4.99997 5 5.22383 5 5.49997C5 5.77611 4.77614 5.99997 4.5 5.99997H1.5C1.22386 5.99997 1 5.77611 1 5.49997V2.49997C1 2.22383 1.22386 1.99997 1.5 1.99997C1.77614 1.99997 2 2.22383 2 2.49997V4.31318C2.70453 3.07126 4.33406 0.849976 7.50002 0.849976C11.5628 0.849976 14.15 4.18537 14.15 7.49998C14.15 10.8146 11.5628 14.15 7.50002 14.15C5.55618 14.15 3.93778 13.3808 2.78548 12.2084C2.16852 11.5806 1.68668 10.839 1.35816 10.0407C1.25306 9.78536 1.37488 9.49315 1.63024 9.38806C1.8856 9.28296 2.17781 9.40478 2.2829 9.66014C2.56374 10.3425 2.97495 10.9745 3.4987 11.5074C4.47052 12.4963 5.83496 13.15 7.50002 13.15C10.9402 13.15 13.15 10.3354 13.15 7.49998ZM7 10V5.00001H8V10H7Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 974 B

View file

@ -6,11 +6,8 @@ mod prompts;
mod saved_conversation; mod saved_conversation;
mod streaming_diff; mod streaming_diff;
mod embedded_scope;
pub use assistant_panel::AssistantPanel; pub use assistant_panel::AssistantPanel;
use assistant_settings::{AssistantSettings, OpenAiModel, ZedDotDevModel}; use assistant_settings::{AssistantSettings, OpenAiModel, ZedDotDevModel};
use chrono::{DateTime, Local};
use client::{proto, Client}; use client::{proto, Client};
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
pub(crate) use completion_provider::*; pub(crate) use completion_provider::*;
@ -26,7 +23,6 @@ use std::{
actions!( actions!(
assistant, assistant,
[ [
NewConversation,
Assist, Assist,
Split, Split,
CycleMessageRole, CycleMessageRole,
@ -35,6 +31,7 @@ actions!(
ResetKey, ResetKey,
InlineAssist, InlineAssist,
ToggleIncludeConversation, ToggleIncludeConversation,
ToggleHistory,
] ]
); );
@ -93,8 +90,8 @@ impl LanguageModel {
pub fn display_name(&self) -> String { pub fn display_name(&self) -> String {
match self { match self {
LanguageModel::OpenAi(model) => format!("openai/{}", model.display_name()), LanguageModel::OpenAi(model) => model.display_name().into(),
LanguageModel::ZedDotDev(model) => format!("zed.dev/{}", model.display_name()), LanguageModel::ZedDotDev(model) => model.display_name().into(),
} }
} }
@ -178,7 +175,6 @@ pub struct LanguageModelChoiceDelta {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
struct MessageMetadata { struct MessageMetadata {
role: Role, role: Role,
sent_at: DateTime<Local>,
status: MessageStatus, status: MessageStatus,
} }

File diff suppressed because it is too large Load diff

View file

@ -1,91 +0,0 @@
use editor::MultiBuffer;
use gpui::{AppContext, Model, ModelContext, Subscription};
use crate::{assistant_panel::Conversation, LanguageModelRequestMessage, Role};
#[derive(Default)]
pub struct EmbeddedScope {
active_buffer: Option<Model<MultiBuffer>>,
active_buffer_enabled: bool,
active_buffer_subscription: Option<Subscription>,
}
impl EmbeddedScope {
pub fn new() -> Self {
Self {
active_buffer: None,
active_buffer_enabled: true,
active_buffer_subscription: None,
}
}
pub fn set_active_buffer(
&mut self,
buffer: Option<Model<MultiBuffer>>,
cx: &mut ModelContext<Conversation>,
) {
self.active_buffer_subscription.take();
if let Some(active_buffer) = buffer.clone() {
self.active_buffer_subscription =
Some(cx.subscribe(&active_buffer, |conversation, _, e, cx| {
if let multi_buffer::Event::Edited { .. } = e {
conversation.count_remaining_tokens(cx)
}
}));
}
self.active_buffer = buffer;
}
pub fn active_buffer(&self) -> Option<&Model<MultiBuffer>> {
self.active_buffer.as_ref()
}
pub fn active_buffer_enabled(&self) -> bool {
self.active_buffer_enabled
}
pub fn set_active_buffer_enabled(&mut self, enabled: bool) {
self.active_buffer_enabled = enabled;
}
/// Provide a message for the language model based on the active buffer.
pub fn message(&self, cx: &AppContext) -> Option<LanguageModelRequestMessage> {
if !self.active_buffer_enabled {
return None;
}
let active_buffer = self.active_buffer.as_ref()?;
let buffer = active_buffer.read(cx);
if let Some(singleton) = buffer.as_singleton() {
let singleton = singleton.read(cx);
let filename = singleton
.file()
.map(|file| file.path().to_string_lossy())
.unwrap_or("Untitled".into());
let text = singleton.text();
let language = singleton
.language()
.map(|l| {
let name = l.code_fence_block_name();
name.to_string()
})
.unwrap_or_default();
let markdown =
format!("User's active file `{filename}`:\n\n```{language}\n{text}```\n\n");
return Some(LanguageModelRequestMessage {
role: Role::System,
content: markdown,
});
}
None
}
}

View file

@ -200,6 +200,18 @@ impl FocusHandle {
pub fn contains(&self, other: &Self, cx: &WindowContext) -> bool { pub fn contains(&self, other: &Self, cx: &WindowContext) -> bool {
self.id.contains(other.id, cx) self.id.contains(other.id, cx)
} }
/// Dispatch an action on the element that rendered this focus handle
pub fn dispatch_action(&self, action: &dyn Action, cx: &mut WindowContext) {
if let Some(node_id) = cx
.window
.rendered_frame
.dispatch_tree
.focusable_node_id(self.id)
{
cx.dispatch_action_on_node(node_id, action)
}
}
} }
impl Clone for FocusHandle { impl Clone for FocusHandle {

View file

@ -189,8 +189,8 @@ impl TabSwitcherDelegate {
let pane = pane.read(cx); let pane = pane.read(cx);
let mut history_indices = HashMap::default(); let mut history_indices = HashMap::default();
pane.activation_history().iter().rev().enumerate().for_each( pane.activation_history().iter().rev().enumerate().for_each(
|(history_index, entity_id)| { |(history_index, history_entry)| {
history_indices.insert(entity_id, history_index); history_indices.insert(history_entry.entity_id, history_index);
}, },
); );

View file

@ -107,6 +107,7 @@ pub enum IconName {
CopilotError, CopilotError,
CopilotInit, CopilotInit,
Copy, Copy,
CountdownTimer,
Dash, Dash,
Delete, Delete,
Disconnected, Disconnected,
@ -221,6 +222,7 @@ impl IconName {
IconName::CopilotError => "icons/copilot_error.svg", IconName::CopilotError => "icons/copilot_error.svg",
IconName::CopilotInit => "icons/copilot_init.svg", IconName::CopilotInit => "icons/copilot_init.svg",
IconName::Copy => "icons/copy.svg", IconName::Copy => "icons/copy.svg",
IconName::CountdownTimer => "icons/countdown_timer.svg",
IconName::Dash => "icons/dash.svg", IconName::Dash => "icons/dash.svg",
IconName::Delete => "icons/delete.svg", IconName::Delete => "icons/delete.svg",
IconName::Disconnected => "icons/disconnected.svg", IconName::Disconnected => "icons/disconnected.svg",

View file

@ -191,7 +191,8 @@ pub struct Pane {
), ),
focus_handle: FocusHandle, focus_handle: FocusHandle,
items: Vec<Box<dyn ItemHandle>>, items: Vec<Box<dyn ItemHandle>>,
activation_history: Vec<EntityId>, activation_history: Vec<ActivationHistoryEntry>,
next_activation_timestamp: Arc<AtomicUsize>,
zoomed: bool, zoomed: bool,
was_focused: bool, was_focused: bool,
active_item_index: usize, active_item_index: usize,
@ -219,6 +220,11 @@ pub struct Pane {
double_click_dispatch_action: Box<dyn Action>, double_click_dispatch_action: Box<dyn Action>,
} }
pub struct ActivationHistoryEntry {
pub entity_id: EntityId,
pub timestamp: usize,
}
pub struct ItemNavHistory { pub struct ItemNavHistory {
history: NavHistory, history: NavHistory,
item: Arc<dyn WeakItemHandle>, item: Arc<dyn WeakItemHandle>,
@ -296,6 +302,7 @@ impl Pane {
focus_handle, focus_handle,
items: Vec::new(), items: Vec::new(),
activation_history: Vec::new(), activation_history: Vec::new(),
next_activation_timestamp: next_timestamp.clone(),
was_focused: false, was_focused: false,
zoomed: false, zoomed: false,
active_item_index: 0, active_item_index: 0,
@ -506,7 +513,7 @@ impl Pane {
self.active_item_index self.active_item_index
} }
pub fn activation_history(&self) -> &Vec<EntityId> { pub fn activation_history(&self) -> &[ActivationHistoryEntry] {
&self.activation_history &self.activation_history
} }
@ -892,10 +899,13 @@ impl Pane {
if let Some(newly_active_item) = self.items.get(index) { if let Some(newly_active_item) = self.items.get(index) {
self.activation_history self.activation_history
.retain(|&previously_active_item_id| { .retain(|entry| entry.entity_id != newly_active_item.item_id());
previously_active_item_id != newly_active_item.item_id() self.activation_history.push(ActivationHistoryEntry {
}); entity_id: newly_active_item.item_id(),
self.activation_history.push(newly_active_item.item_id()); timestamp: self
.next_activation_timestamp
.fetch_add(1, Ordering::SeqCst),
});
} }
self.update_toolbar(cx); self.update_toolbar(cx);
@ -1211,7 +1221,7 @@ impl Pane {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
self.activation_history self.activation_history
.retain(|&history_entry| history_entry != self.items[item_index].item_id()); .retain(|entry| entry.entity_id != self.items[item_index].item_id());
if item_index == self.active_item_index { if item_index == self.active_item_index {
let index_to_activate = self let index_to_activate = self
@ -1219,7 +1229,7 @@ impl Pane {
.pop() .pop()
.and_then(|last_activated_item| { .and_then(|last_activated_item| {
self.items.iter().enumerate().find_map(|(index, item)| { self.items.iter().enumerate().find_map(|(index, item)| {
(item.item_id() == last_activated_item).then_some(index) (item.item_id() == last_activated_item.entity_id).then_some(index)
}) })
}) })
// We didn't have a valid activation history entry, so fallback // We didn't have a valid activation history entry, so fallback

View file

@ -532,6 +532,9 @@ impl DelayedDebouncedEditAction {
pub enum Event { pub enum Event {
PaneAdded(View<Pane>), PaneAdded(View<Pane>),
PaneRemoved,
ItemAdded,
ItemRemoved,
ActiveItemChanged, ActiveItemChanged,
ContactRequestedJoin(u64), ContactRequestedJoin(u64),
WorkspaceCreated(WeakView<Workspace>), WorkspaceCreated(WeakView<Workspace>),
@ -2513,7 +2516,10 @@ impl Workspace {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
pane::Event::AddItem { item } => item.added_to_pane(self, pane, cx), pane::Event::AddItem { item } => {
item.added_to_pane(self, pane, cx);
cx.emit(Event::ItemAdded);
}
pane::Event::Split(direction) => { pane::Event::Split(direction) => {
self.split_and_clone(pane, *direction, cx); self.split_and_clone(pane, *direction, cx);
} }
@ -2696,6 +2702,7 @@ impl Workspace {
} else { } else {
self.active_item_path_changed(cx); self.active_item_path_changed(cx);
} }
cx.emit(Event::PaneRemoved);
} }
pub fn panes(&self) -> &[View<Pane>] { pub fn panes(&self) -> &[View<Pane>] {