3312 lines · 130.4 KB · Rust
RawDownload
use std::ops::Range;use std::path::Path;use std::rc::Rc;use std::sync::Arc;use std::time::Duration; use db::kvp::{Dismissable, KEY_VALUE_STORE};use serde::{Deserialize, Serialize}; use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};use anyhow::{Result, anyhow};use assistant_context_editor::{    AgentPanelDelegate, AssistantContext, ConfigurationError, ContextEditor, ContextEvent,    ContextSummary, SlashCommandCompletionProvider, humanize_token_count,    make_lsp_adapter_delegate, render_remaining_tokens,};use assistant_slash_command::SlashCommandWorkingSet;use assistant_tool::ToolWorkingSet; use assistant_context_editor::language_model_selector::ToggleModelSelector;use client::{UserStore, zed_urls};use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};use fs::Fs;use gpui::{    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,    Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, FontWeight,    KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop,    linear_gradient, prelude::*, pulsating_between,};use language::LanguageRegistry;use language_model::{    LanguageModelProviderTosView, LanguageModelRegistry, RequestUsage, ZED_CLOUD_PROVIDER_ID,};use project::{Project, ProjectPath, Worktree};use prompt_store::{PromptBuilder, PromptStore, UserPromptId};use proto::Plan;use rules_library::{RulesLibrary, open_rules_library};use search::{BufferSearchBar, buffer_search};use settings::{Settings, update_settings_file};use theme::ThemeSettings;use time::UtcOffset;use ui::utils::WithRemSize;use ui::{    Banner, CheckboxWithLabel, ContextMenu, ElevationIndex, KeyBinding, PopoverMenu,    PopoverMenuHandle, ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,};use util::{ResultExt as _, maybe};use workspace::dock::{DockPosition, Panel, PanelEvent};use workspace::{    CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,};use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};use zed_llm_client::{CompletionIntent, UsageLimit}; use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};use crate::agent_diff::AgentDiff;use crate::history_store::{HistoryStore, RecentEntry};use crate::message_editor::{MessageEditor, MessageEditorEvent};use crate::thread::{Thread, ThreadError, ThreadId, ThreadSummary, TokenUsageRatio};use crate::thread_history::{HistoryEntryElement, ThreadHistory};use crate::thread_store::ThreadStore;use crate::ui::AgentOnboardingModal;use crate::{    AddContextServer, AgentDiffPane, ContextStore, ContinueThread, ContinueWithBurnMode,    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,    ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleBurnMode, ToggleContextPicker,    ToggleNavigationMenu, ToggleOptionsMenu,}; const AGENT_PANEL_KEY: &str = "agent_panel"; #[derive(Serialize, Deserialize)]struct SerializedAgentPanel {    width: Option<Pixels>,} pub fn init(cx: &mut App) {    cx.observe_new(        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {            workspace                .register_action(|workspace, action: &NewThread, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));                        workspace.focus_panel::<AgentPanel>(window, cx);                    }                })                .register_action(|workspace, _: &OpenHistory, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.open_history(window, cx));                    }                })                .register_action(|workspace, _: &OpenConfiguration, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));                    }                })                .register_action(|workspace, _: &NewTextThread, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));                    }                })                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.deploy_rules_library(action, window, cx)                        });                    }                })                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        let thread = panel.read(cx).thread.read(cx).thread().clone();                        AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);                    }                })                .register_action(|workspace, _: &Follow, window, cx| {                    workspace.follow(CollaboratorId::Agent, window, cx);                })                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.message_editor.update(cx, |editor, cx| {                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);                            });                        });                    }                })                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);                        });                    }                })                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {                        workspace.focus_panel::<AgentPanel>(window, cx);                        panel.update(cx, |panel, cx| {                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);                        });                    }                })                .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {                    AgentOnboardingModal::toggle(workspace, window, cx)                })                .register_action(|_workspace, _: &ResetOnboarding, window, cx| {                    window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);                    window.refresh();                })                .register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {                    Upsell::set_dismissed(false, cx);                })                .register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {                    TrialEndUpsell::set_dismissed(false, cx);                });        },    )    .detach();} enum ActiveView {    Thread {        change_title_editor: Entity<Editor>,        thread: WeakEntity<Thread>,        _subscriptions: Vec<gpui::Subscription>,    },    TextThread {        context_editor: Entity<ContextEditor>,        title_editor: Entity<Editor>,        buffer_search_bar: Entity<BufferSearchBar>,        _subscriptions: Vec<gpui::Subscription>,    },    History,    Configuration,} enum WhichFontSize {    AgentFont,    BufferFont,    None,} impl ActiveView {    pub fn which_font_size_used(&self) -> WhichFontSize {        match self {            ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,            ActiveView::TextThread { .. } => WhichFontSize::BufferFont,            ActiveView::Configuration => WhichFontSize::None,        }    }     pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {        let summary = thread.read(cx).summary().or_default();         let editor = cx.new(|cx| {            let mut editor = Editor::single_line(window, cx);            editor.set_text(summary.clone(), window, cx);            editor        });         let subscriptions = vec![            window.subscribe(&editor, cx, {                {                    let thread = thread.clone();                    move |editor, event, window, cx| match event {                        EditorEvent::BufferEdited => {                            let new_summary = editor.read(cx).text(cx);                             thread.update(cx, |thread, cx| {                                thread.set_summary(new_summary, cx);                            })                        }                        EditorEvent::Blurred => {                            if editor.read(cx).text(cx).is_empty() {                                let summary = thread.read(cx).summary().or_default();                                 editor.update(cx, |editor, cx| {                                    editor.set_text(summary, window, cx);                                });                            }                        }                        _ => {}                    }                }            }),            window.subscribe(&thread, cx, {                let editor = editor.clone();                move |thread, event, window, cx| match event {                    ThreadEvent::SummaryGenerated => {                        let summary = thread.read(cx).summary().or_default();                         editor.update(cx, |editor, cx| {                            editor.set_text(summary, window, cx);                        })                    }                    _ => {}                }            }),        ];         Self::Thread {            change_title_editor: editor,            thread: thread.downgrade(),            _subscriptions: subscriptions,        }    }     pub fn prompt_editor(        context_editor: Entity<ContextEditor>,        language_registry: Arc<LanguageRegistry>,        window: &mut Window,        cx: &mut App,    ) -> Self {        let title = context_editor.read(cx).title(cx).to_string();         let editor = cx.new(|cx| {            let mut editor = Editor::single_line(window, cx);            editor.set_text(title, window, cx);            editor        });         // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would        // cause a custom summary to be set. The presence of this custom summary would cause        // summarization to not happen.        let mut suppress_first_edit = true;         let subscriptions = vec![            window.subscribe(&editor, cx, {                {                    let context_editor = context_editor.clone();                    move |editor, event, window, cx| match event {                        EditorEvent::BufferEdited => {                            if suppress_first_edit {                                suppress_first_edit = false;                                return;                            }                            let new_summary = editor.read(cx).text(cx);                             context_editor.update(cx, |context_editor, cx| {                                context_editor                                    .context()                                    .update(cx, |assistant_context, cx| {                                        assistant_context.set_custom_summary(new_summary, cx);                                    })                            })                        }                        EditorEvent::Blurred => {                            if editor.read(cx).text(cx).is_empty() {                                let summary = context_editor                                    .read(cx)                                    .context()                                    .read(cx)                                    .summary()                                    .or_default();                                 editor.update(cx, |editor, cx| {                                    editor.set_text(summary, window, cx);                                });                            }                        }                        _ => {}                    }                }            }),            window.subscribe(&context_editor.read(cx).context().clone(), cx, {                let editor = editor.clone();                move |assistant_context, event, window, cx| match event {                    ContextEvent::SummaryGenerated => {                        let summary = assistant_context.read(cx).summary().or_default();                         editor.update(cx, |editor, cx| {                            editor.set_text(summary, window, cx);                        })                    }                    _ => {}                }            }),        ];         let buffer_search_bar =            cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));        buffer_search_bar.update(cx, |buffer_search_bar, cx| {            buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)        });         Self::TextThread {            context_editor,            title_editor: editor,            buffer_search_bar,            _subscriptions: subscriptions,        }    }} pub struct AgentPanel {    workspace: WeakEntity<Workspace>,    user_store: Entity<UserStore>,    project: Entity<Project>,    fs: Arc<dyn Fs>,    language_registry: Arc<LanguageRegistry>,    thread_store: Entity<ThreadStore>,    thread: Entity<ActiveThread>,    message_editor: Entity<MessageEditor>,    _active_thread_subscriptions: Vec<Subscription>,    _default_model_subscription: Subscription,    context_store: Entity<TextThreadStore>,    prompt_store: Option<Entity<PromptStore>>,    inline_assist_context_store: Entity<crate::context_store::ContextStore>,    configuration: Option<Entity<AgentConfiguration>>,    configuration_subscription: Option<Subscription>,    local_timezone: UtcOffset,    active_view: ActiveView,    previous_view: Option<ActiveView>,    history_store: Entity<HistoryStore>,    history: Entity<ThreadHistory>,    hovered_recent_history_item: Option<usize>,    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,    assistant_navigation_menu: Option<Entity<ContextMenu>>,    width: Option<Pixels>,    height: Option<Pixels>,    zoomed: bool,    pending_serialization: Option<Task<Result<()>>>,    hide_upsell: bool,} impl AgentPanel {    fn serialize(&mut self, cx: &mut Context<Self>) {        let width = self.width;        self.pending_serialization = Some(cx.background_spawn(async move {            KEY_VALUE_STORE                .write_kvp(                    AGENT_PANEL_KEY.into(),                    serde_json::to_string(&SerializedAgentPanel { width })?,                )                .await?;            anyhow::Ok(())        }));    }    pub fn load(        workspace: WeakEntity<Workspace>,        prompt_builder: Arc<PromptBuilder>,        mut cx: AsyncWindowContext,    ) -> Task<Result<Entity<Self>>> {        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));        cx.spawn(async move |cx| {            let prompt_store = match prompt_store {                Ok(prompt_store) => prompt_store.await.ok(),                Err(_) => None,            };            let tools = cx.new(|_| ToolWorkingSet::default())?;            let thread_store = workspace                .update(cx, |workspace, cx| {                    let project = workspace.project().clone();                    ThreadStore::load(                        project,                        tools.clone(),                        prompt_store.clone(),                        prompt_builder.clone(),                        cx,                    )                })?                .await?;             let slash_commands = Arc::new(SlashCommandWorkingSet::default());            let context_store = workspace                .update(cx, |workspace, cx| {                    let project = workspace.project().clone();                    assistant_context_editor::ContextStore::new(                        project,                        prompt_builder.clone(),                        slash_commands,                        cx,                    )                })?                .await?;             let serialized_panel = if let Some(panel) = cx                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })                .await                .log_err()                .flatten()            {                Some(serde_json::from_str::<SerializedAgentPanel>(&panel)?)            } else {                None            };             let panel = workspace.update_in(cx, |workspace, window, cx| {                let panel = cx.new(|cx| {                    Self::new(                        workspace,                        thread_store,                        context_store,                        prompt_store,                        window,                        cx,                    )                });                if let Some(serialized_panel) = serialized_panel {                    panel.update(cx, |panel, cx| {                        panel.width = serialized_panel.width.map(|w| w.round());                        cx.notify();                    });                }                panel            })?;             Ok(panel)        })    }     fn new(        workspace: &Workspace,        thread_store: Entity<ThreadStore>,        context_store: Entity<TextThreadStore>,        prompt_store: Option<Entity<PromptStore>>,        window: &mut Window,        cx: &mut Context<Self>,    ) -> Self {        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));        let fs = workspace.app_state().fs.clone();        let user_store = workspace.app_state().user_store.clone();        let project = workspace.project();        let language_registry = project.read(cx).languages().clone();        let workspace = workspace.weak_handle();        let weak_self = cx.entity().downgrade();         let message_editor_context_store = cx.new(|_cx| {            crate::context_store::ContextStore::new(                project.downgrade(),                Some(thread_store.downgrade()),            )        });        let inline_assist_context_store = cx.new(|_cx| {            crate::context_store::ContextStore::new(                project.downgrade(),                Some(thread_store.downgrade()),            )        });         let message_editor = cx.new(|cx| {            MessageEditor::new(                fs.clone(),                workspace.clone(),                user_store.clone(),                message_editor_context_store.clone(),                prompt_store.clone(),                thread_store.downgrade(),                context_store.downgrade(),

Lines 1–500 of 3312.