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

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

View file

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

View file

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

View file

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

View file

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