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:
parent
e4c95b25bf
commit
a13a92fbbf
9 changed files with 522 additions and 411 deletions
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>] {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue