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 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>] {