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. Load more
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(),