Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Benfield
a7cf3c306a in progress 2025-11-19 17:03:48 -08:00
13 changed files with 2057 additions and 109 deletions

2
Cargo.lock generated
View File

@@ -327,6 +327,7 @@ dependencies = [
"buffer_diff",
"chrono",
"client",
"clock",
"cloud_llm_client",
"collections",
"command_palette_hooks",
@@ -342,6 +343,7 @@ dependencies = [
"futures 0.3.31",
"fuzzy",
"gpui",
"gpui_tokio",
"html_to_markdown",
"http_client",
"image",

View File

@@ -14,6 +14,7 @@ doctest = false
[features]
test-support = ["gpui/test-support", "language/test-support"]
unit-eval = []
[dependencies]
acp_thread.workspace = true
@@ -47,6 +48,7 @@ fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
gpui.workspace = true
gpui_tokio.workspace = true
html_to_markdown.workspace = true
http_client.workspace = true
indoc.workspace = true
@@ -106,6 +108,7 @@ acp_thread = { workspace = true, features = ["test-support"] }
agent = { workspace = true, features = ["test-support"] }
assistant_text_thread = { workspace = true, features = ["test-support"] }
buffer_diff = { workspace = true, features = ["test-support"] }
clock.workspace = true
db = { workspace = true, features = ["test-support"] }
editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, "features" = ["test-support"] }

View File

@@ -19,7 +19,6 @@ use settings::{
use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
use crate::{
AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, Follow, InlineAssistant,
NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory,
@@ -39,6 +38,10 @@ use crate::{
ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary, placeholder_command,
};
use crate::{ManageProfiles, context_store::ContextStore};
use crate::{
inline_assistant::ContextProviders,
ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal},
};
use agent_settings::AgentSettings;
use ai_onboarding::AgentPanelOnboarding;
use anyhow::{Result, anyhow};
@@ -455,6 +458,10 @@ pub struct AgentPanel {
}
impl AgentPanel {
pub(crate) fn workspace(&self) -> WeakEntity<Workspace> {
self.workspace.clone()
}
fn serialize(&mut self, cx: &mut Context<Self>) {
let width = self.width;
let selected_agent = self.selected_agent.clone();
@@ -2700,17 +2707,12 @@ impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
else {
return;
};
let prompt_store = None;
let thread_store = None;
let context_store = cx.new(|_| ContextStore::new(project.clone()));
let context_providers = ContextProviders::empty(project, cx);
assistant.assist(
prompt_editor,
self.workspace.clone(),
context_store,
project,
prompt_store,
thread_store,
initial_prompt,
context_providers,
self.workspace.clone(),
window,
cx,
)

View File

@@ -9,6 +9,8 @@ mod context_picker;
mod context_server_configuration;
mod context_store;
mod context_strip;
#[cfg(test)]
mod evals;
mod inline_assistant;
mod inline_prompt_editor;
mod language_model_selector;

View File

@@ -38,6 +38,7 @@ use util::rel_path::RelPath;
use workspace::{Workspace, notifications::NotifyResultExt};
use crate::context_picker::thread_context_picker::ThreadContextPicker;
use crate::inline_assistant::ContextProviders;
use crate::{context::RULES_ICON, context_store::ContextStore};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -172,12 +173,18 @@ pub(super) struct ContextPicker {
impl ContextPicker {
pub fn new(
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<HistoryStore>>,
prompt_store: Option<WeakEntity<PromptStore>>,
context_store: WeakEntity<ContextStore>,
context_stores: ContextProviders,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let context_store = context_stores.context_store.downgrade();
let thread_store = context_stores.thread_store.clone();
let prompt_store = context_stores
.prompt_store
.as_ref()
.map(Entity::downgrade)
.clone();
let subscriptions = context_store
.upgrade()
.map(|context_store| {

View File

@@ -27,6 +27,7 @@ use util::paths::PathStyle;
use util::rel_path::RelPath;
use workspace::Workspace;
use crate::inline_assistant::ContextProviders;
use crate::{
context::{AgentContextHandle, AgentContextKey, RULES_ICON},
context_store::ContextStore,
@@ -245,17 +246,15 @@ pub struct ContextPickerCompletionProvider {
impl ContextPickerCompletionProvider {
pub fn new(
workspace: WeakEntity<Workspace>,
context_store: WeakEntity<ContextStore>,
thread_store: Option<WeakEntity<HistoryStore>>,
prompt_store: Option<WeakEntity<PromptStore>>,
context_stores: ContextProviders,
editor: WeakEntity<Editor>,
exclude_buffer: Option<WeakEntity<Buffer>>,
) -> Self {
Self {
workspace,
context_store,
thread_store,
prompt_store,
context_store: context_stores.context_store.downgrade(),
thread_store: context_stores.thread_store,
prompt_store: context_stores.prompt_store.as_ref().map(Entity::downgrade),
editor,
excluded_buffer: exclude_buffer,
}
@@ -1292,7 +1291,7 @@ mod tests {
editor
});
let context_store = cx.new(|_| ContextStore::new(project.downgrade()));
let context_providers = ContextProviders::empty(project.downgrade(), cx);
let editor_entity = editor.downgrade();
editor.update_in(&mut cx, |editor, window, cx| {
@@ -1307,11 +1306,10 @@ mod tests {
.map(Entity::downgrade)
});
window.focus(&editor.focus_handle(cx));
editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
workspace.downgrade(),
context_store.downgrade(),
None,
None,
context_providers,
editor_entity,
last_opened_buffer,
))));

View File

@@ -39,6 +39,10 @@ pub enum ContextStoreEvent {
impl EventEmitter<ContextStoreEvent> for ContextStore {}
impl ContextStore {
pub fn project(&self) -> WeakEntity<Project> {
self.project.clone()
}
pub fn new(project: WeakEntity<Project>) -> Self {
Self {
project,

View File

@@ -2,13 +2,13 @@ use crate::{
AcceptSuggestedContext, AgentPanel, FocusDown, FocusLeft, FocusRight, FocusUp,
ModelUsageContext, RemoveAllContext, RemoveFocusedContext, ToggleContextPicker,
context_picker::ContextPicker,
inline_assistant::ContextProviders,
ui::{AddedContext, ContextPill},
};
use crate::{
context::AgentContextHandle,
context_store::{ContextStore, SuggestedContext},
};
use agent::HistoryStore;
use collections::HashSet;
use editor::Editor;
use gpui::{
@@ -42,10 +42,8 @@ pub struct ContextStrip {
impl ContextStrip {
pub fn new(
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<HistoryStore>>,
prompt_store: Option<WeakEntity<PromptStore>>,
inline_services: ContextProviders,
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
suggest_context_kind: SuggestContextKind,
model_usage_context: ModelUsageContext,

1869
crates/agent_ui/src/evals.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -16,6 +16,7 @@ use agent_settings::AgentSettings;
use anyhow::{Context as _, Result};
use client::telemetry::Telemetry;
use collections::{HashMap, HashSet, VecDeque, hash_map};
use editor::EditorSnapshot;
use editor::RowExt;
use editor::SelectionEffects;
use editor::scroll::ScrollOffset;
@@ -87,6 +88,38 @@ enum InlineAssistTarget {
Terminal(Entity<TerminalView>),
}
#[derive(Clone)]
pub(crate) struct ContextProviders {
pub(crate) workspace: Option<WeakEntity<Workspace>>,
pub(crate) prompt_store: Option<Entity<PromptStore>>,
pub(crate) thread_store: Option<WeakEntity<HistoryStore>>,
pub(crate) context_store: Entity<ContextStore>,
}
impl ContextProviders {
pub(crate) fn project(&self, cx: &mut App) -> WeakEntity<Project> {
self.context_store.read(cx).project()
}
pub(crate) fn from_panel(agent_panel: &AgentPanel) -> Self {
Self {
workspace: Some(agent_panel.workspace()),
prompt_store: agent_panel.prompt_store().as_ref().cloned(),
thread_store: Some(agent_panel.thread_store().downgrade()),
context_store: agent_panel.inline_assist_context_store().clone(),
}
}
pub(crate) fn empty(project: WeakEntity<Project>, cx: &mut App) -> Self {
Self {
workspace: None,
prompt_store: None,
thread_store: None,
context_store: cx.new(|_| ContextStore::new(project.clone())),
}
}
}
pub struct InlineAssistant {
next_assist_id: InlineAssistId,
next_assist_group_id: InlineAssistGroupId,
@@ -274,11 +307,8 @@ impl InlineAssistant {
let Some(agent_panel) = workspace.panel::<AgentPanel>(cx) else {
return;
};
let agent_panel = agent_panel.read(cx);
let prompt_store = agent_panel.prompt_store().as_ref().cloned();
let thread_store = Some(agent_panel.thread_store().downgrade());
let context_store = agent_panel.inline_assist_context_store().clone();
let services = ContextProviders::from_panel(agent_panel.read(cx));
let handle_assist =
|window: &mut Window, cx: &mut Context<Workspace>| match inline_assist_target {
@@ -287,10 +317,6 @@ impl InlineAssistant {
assistant.assist(
&active_editor,
cx.entity().downgrade(),
context_store,
workspace.project().downgrade(),
prompt_store,
thread_store,
action.prompt.clone(),
window,
cx,
@@ -302,10 +328,8 @@ impl InlineAssistant {
assistant.assist(
&active_terminal,
cx.entity().downgrade(),
workspace.project().downgrade(),
prompt_store,
thread_store,
action.prompt.clone(),
services,
window,
cx,
)
@@ -350,25 +374,20 @@ impl InlineAssistant {
}
}
pub fn assist(
fn codegen_ranges(
&mut self,
editor: &Entity<Editor>,
workspace: WeakEntity<Workspace>,
context_store: Entity<ContextStore>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
thread_store: Option<WeakEntity<HistoryStore>>,
initial_prompt: Option<String>,
snapshot: &EditorSnapshot,
window: &mut Window,
cx: &mut App,
) {
let (snapshot, initial_selections, newest_selection) = editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let selections = editor.selections.all::<Point>(&snapshot.display_snapshot);
let newest_selection = editor
.selections
.newest::<Point>(&snapshot.display_snapshot);
(snapshot, selections, newest_selection)
) -> Option<(Vec<Range<Anchor>>, Selection<Point>)> {
let (initial_selections, newest_selection) = editor.update(cx, |editor, _| {
(
editor.selections.all::<Point>(&snapshot.display_snapshot),
editor
.selections
.newest::<Point>(&snapshot.display_snapshot),
)
});
// Check if there is already an inline assistant that contains the
@@ -381,7 +400,7 @@ impl InlineAssistant {
&& newest_selection.end.row <= range.end.row
{
self.focus_assist(*assist_id, window, cx);
return;
return None;
}
}
}
@@ -473,6 +492,25 @@ impl InlineAssistant {
}
}
Some((codegen_ranges, newest_selection))
}
pub fn assist(
&mut self,
editor: &Entity<Editor>,
initial_prompt: Option<String>,
services: ContextProviders,
window: &mut Window,
cx: &mut App,
) {
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(window, cx));
let Some((codegen_ranges, newest_selection)) =
self.codegen_ranges(editor, &snapshot, window, cx)
else {
return;
};
let assist_group_id = self.next_assist_group_id.post_inc();
let prompt_buffer = cx.new(|cx| {
MultiBuffer::singleton(
@@ -484,53 +522,26 @@ impl InlineAssistant {
let mut assists = Vec::new();
let mut assist_to_focus = None;
for range in codegen_ranges {
let assist_id = self.next_assist_id.post_inc();
let codegen = cx.new(|cx| {
BufferCodegen::new(
editor.read(cx).buffer().clone(),
range.clone(),
None,
context_store.clone(),
project.clone(),
prompt_store.clone(),
self.telemetry.clone(),
self.prompt_builder.clone(),
cx,
)
});
let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new_buffer(
assist_id,
editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
self.fs.clone(),
context_store.clone(),
workspace.clone(),
thread_store.clone(),
prompt_store.as_ref().map(|s| s.downgrade()),
window,
cx,
)
});
let (assist_id, prompt_block_id, end_block_id, prompt_editor) = self.single_assist(
range.clone(),
editor,
prompt_buffer.clone(),
services.clone(),
window,
cx,
);
if assist_to_focus.is_none() {
let focus_assist = if newest_selection.reversed {
range.start.to_point(snapshot) == newest_selection.start
range.start.to_point(&snapshot) == newest_selection.start
} else {
range.end.to_point(snapshot) == newest_selection.end
range.end.to_point(&snapshot) == newest_selection.end
};
if focus_assist {
assist_to_focus = Some(assist_id);
}
}
let [prompt_block_id, end_block_id] =
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
assists.push((
assist_id,
range,
@@ -574,11 +585,66 @@ impl InlineAssistant {
}
}
fn single_assist(
&mut self,
range: Range<Anchor>,
editor: &Entity<Editor>,
workspace: WeakEntity<Workspace>,
prompt_buffer: Entity<MultiBuffer>,
services: ContextProviders,
project: &WeakEntity<Project>,
window: &mut Window,
cx: &mut App,
) -> (
InlineAssistId,
CustomBlockId,
CustomBlockId,
Entity<PromptEditor<BufferCodegen>>,
) {
let assist_id = self.next_assist_id.post_inc();
let codegen = cx.new(|cx| {
BufferCodegen::new(
editor.read(cx).buffer().clone(),
range.clone(),
None,
context_store.clone(),
project.clone(),
prompt_store.clone(),
self.telemetry.clone(),
self.prompt_builder.clone(),
cx,
)
});
let editor_margins = Arc::new(Mutex::new(EditorMargins::default()));
let prompt_editor = cx.new(|cx| {
PromptEditor::new_buffer(
assist_id,
editor_margins,
self.prompt_history.clone(),
prompt_buffer.clone(),
codegen.clone(),
self.fs.clone(),
context_store.clone(),
workspace.clone(),
thread_store.clone(),
prompt_store.as_ref().map(|s| s.downgrade()),
window,
cx,
)
});
let [prompt_block_id, end_block_id] =
self.insert_assist_blocks(editor, &range, &prompt_editor, cx);
(assist_id, prompt_block_id, end_block_id, prompt_editor)
}
pub fn suggest_assist(
&mut self,
editor: &Entity<Editor>,
mut range: Range<Anchor>,
initial_prompt: String,
prompt_buffer: Entity<MultiBuffer>,
initial_transaction_id: Option<TransactionId>,
focus: bool,
workspace: Entity<Workspace>,
@@ -587,9 +653,8 @@ impl InlineAssistant {
window: &mut Window,
cx: &mut App,
) -> InlineAssistId {
/// !!!!!!!!
let assist_group_id = self.next_assist_group_id.post_inc();
let prompt_buffer = cx.new(|cx| Buffer::local(&initial_prompt, cx));
let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let assist_id = self.next_assist_id.post_inc();
@@ -1879,11 +1944,15 @@ impl CodeActionProvider for AssistantCodeActionProvider {
.context("invalid range")?;
let prompt_store = prompt_store.await.ok();
const PROMPT: &'static str = "Fix Diagnostics";
cx.update_global(|assistant: &mut InlineAssistant, window, cx| {
let prompt_buffer = cx.new(|cx| Buffer::local(PROMPT, cx));
let prompt_buffer = cx.new(|cx| MultiBuffer::singleton(prompt_buffer, cx));
let assist_id = assistant.suggest_assist(
&editor,
range,
"Fix Diagnostics".into(),
prompt_buffer,
None,
true,
workspace,

View File

@@ -32,6 +32,7 @@ use crate::context::{AgentContextHandle, AgentContextKey};
use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
use crate::context_store::{ContextStore, ContextStoreEvent};
use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
use crate::inline_assistant::ContextProviders;
use crate::terminal_codegen::TerminalCodegen;
use crate::{
CycleNextInlineAssist, CyclePreviousInlineAssist, ModelUsageContext, RemoveAllContext,
@@ -773,10 +774,8 @@ impl PromptEditor<BufferCodegen> {
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<BufferCodegen>,
fs: Arc<dyn Fs>,
context_store: Entity<ContextStore>,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<HistoryStore>>,
prompt_store: Option<WeakEntity<PromptStore>>,
services: ContextProviders,
window: &mut Window,
cx: &mut Context<PromptEditor<BufferCodegen>>,
) -> PromptEditor<BufferCodegen> {
@@ -945,10 +944,8 @@ impl PromptEditor<TerminalCodegen> {
prompt_buffer: Entity<MultiBuffer>,
codegen: Entity<TerminalCodegen>,
fs: Arc<dyn Fs>,
context_store: Entity<ContextStore>,
context_providers: ContextProviders,
workspace: WeakEntity<Workspace>,
thread_store: Option<WeakEntity<HistoryStore>>,
prompt_store: Option<WeakEntity<PromptStore>>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {

View File

@@ -1,6 +1,7 @@
use crate::{
context::load_context,
context_store::ContextStore,
inline_assistant::ContextProviders,
inline_prompt_editor::{
CodegenStatus, PromptEditor, PromptEditorEvent, TerminalInlineAssistId,
},
@@ -72,10 +73,8 @@ impl TerminalInlineAssistant {
&mut self,
terminal_view: &Entity<TerminalView>,
workspace: WeakEntity<Workspace>,
project: WeakEntity<Project>,
prompt_store: Option<Entity<PromptStore>>,
thread_store: Option<WeakEntity<HistoryStore>>,
initial_prompt: Option<String>,
context_providers: ContextProviders,
window: &mut Window,
cx: &mut App,
) {
@@ -87,7 +86,7 @@ impl TerminalInlineAssistant {
cx,
)
});
let context_store = cx.new(|_cx| ContextStore::new(project));
let codegen = cx.new(|_| TerminalCodegen::new(terminal, self.telemetry.clone()));
let prompt_editor = cx.new(|cx| {
@@ -97,10 +96,8 @@ impl TerminalInlineAssistant {
prompt_buffer.clone(),
codegen,
self.fs.clone(),
context_store.clone(),
context_providers,
workspace.clone(),
thread_store.clone(),
prompt_store.as_ref().map(|s| s.downgrade()),
window,
cx,
)
@@ -420,8 +417,7 @@ impl TerminalInlineAssist {
terminal: &Entity<TerminalView>,
prompt_editor: Entity<PromptEditor<TerminalCodegen>>,
workspace: WeakEntity<Workspace>,
context_store: Entity<ContextStore>,
prompt_store: Option<Entity<PromptStore>>,
context_providers: ContextProviders,
window: &mut Window,
cx: &mut App,
) -> Self {

View File

@@ -966,6 +966,7 @@ impl AppState {
if !cx.has_global::<SettingsStore>() {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
}