assistant_panel.rs

   1use std::ops::Range;
   2use std::path::Path;
   3use std::sync::Arc;
   4use std::time::Duration;
   5
   6use db::kvp::KEY_VALUE_STORE;
   7use serde::{Deserialize, Serialize};
   8
   9use anyhow::{Result, anyhow};
  10use assistant_context_editor::{
  11    AssistantContext, AssistantPanelDelegate, ConfigurationError, ContextEditor, ContextEvent,
  12    SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
  13    render_remaining_tokens,
  14};
  15use assistant_settings::{AssistantDockPosition, AssistantSettings};
  16use assistant_slash_command::SlashCommandWorkingSet;
  17use assistant_tool::ToolWorkingSet;
  18
  19use client::{UserStore, zed_urls};
  20use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  21use fs::Fs;
  22use gpui::{
  23    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  24    Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext,
  25    Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
  26};
  27use language::LanguageRegistry;
  28use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
  29use language_model_selector::ToggleModelSelector;
  30use project::Project;
  31use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  32use proto::Plan;
  33use rules_library::{RulesLibrary, open_rules_library};
  34use settings::{Settings, update_settings_file};
  35use time::UtcOffset;
  36use ui::{
  37    Banner, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*,
  38};
  39use util::ResultExt as _;
  40use workspace::dock::{DockPosition, Panel, PanelEvent};
  41use workspace::{CollaboratorId, Workspace};
  42use zed_actions::agent::OpenConfiguration;
  43use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
  44
  45use crate::active_thread::{ActiveThread, ActiveThreadEvent};
  46use crate::agent_diff::AgentDiff;
  47use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
  48use crate::history_store::{HistoryEntry, HistoryStore, RecentEntry};
  49use crate::message_editor::{MessageEditor, MessageEditorEvent};
  50use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
  51use crate::thread_history::{PastContext, PastThread, ThreadHistory};
  52use crate::thread_store::ThreadStore;
  53use crate::{
  54    AddContextServer, AgentDiffPane, DeleteRecentlyOpenThread, ExpandMessageEditor, Follow,
  55    InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
  56    OpenHistory, ThreadEvent, ToggleContextPicker, ToggleNavigationMenu, ToggleOptionsMenu,
  57};
  58
  59const AGENT_PANEL_KEY: &str = "agent_panel";
  60
  61#[derive(Serialize, Deserialize)]
  62struct SerializedAssistantPanel {
  63    width: Option<Pixels>,
  64}
  65
  66pub fn init(cx: &mut App) {
  67    cx.observe_new(
  68        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  69            workspace
  70                .register_action(|workspace, action: &NewThread, window, cx| {
  71                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  72                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
  73                        workspace.focus_panel::<AssistantPanel>(window, cx);
  74                    }
  75                })
  76                .register_action(|workspace, _: &OpenHistory, window, cx| {
  77                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  78                        workspace.focus_panel::<AssistantPanel>(window, cx);
  79                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
  80                    }
  81                })
  82                .register_action(|workspace, _: &OpenConfiguration, window, cx| {
  83                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  84                        workspace.focus_panel::<AssistantPanel>(window, cx);
  85                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
  86                    }
  87                })
  88                .register_action(|workspace, _: &NewTextThread, window, cx| {
  89                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  90                        workspace.focus_panel::<AssistantPanel>(window, cx);
  91                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
  92                    }
  93                })
  94                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
  95                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  96                        workspace.focus_panel::<AssistantPanel>(window, cx);
  97                        panel.update(cx, |panel, cx| {
  98                            panel.deploy_rules_library(action, window, cx)
  99                        });
 100                    }
 101                })
 102                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
 103                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 104                        workspace.focus_panel::<AssistantPanel>(window, cx);
 105                        let thread = panel.read(cx).thread.read(cx).thread().clone();
 106                        AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
 107                    }
 108                })
 109                .register_action(|workspace, _: &Follow, window, cx| {
 110                    workspace.follow(CollaboratorId::Agent, window, cx);
 111                })
 112                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
 113                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 114                        workspace.focus_panel::<AssistantPanel>(window, cx);
 115                        panel.update(cx, |panel, cx| {
 116                            panel.message_editor.update(cx, |editor, cx| {
 117                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
 118                            });
 119                        });
 120                    }
 121                })
 122                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
 123                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 124                        workspace.focus_panel::<AssistantPanel>(window, cx);
 125                        panel.update(cx, |panel, cx| {
 126                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
 127                        });
 128                    }
 129                })
 130                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
 131                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 132                        workspace.focus_panel::<AssistantPanel>(window, cx);
 133                        panel.update(cx, |panel, cx| {
 134                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);
 135                        });
 136                    }
 137                });
 138        },
 139    )
 140    .detach();
 141}
 142
 143enum ActiveView {
 144    Thread {
 145        change_title_editor: Entity<Editor>,
 146        thread: WeakEntity<Thread>,
 147        _subscriptions: Vec<gpui::Subscription>,
 148    },
 149    PromptEditor {
 150        context_editor: Entity<ContextEditor>,
 151        title_editor: Entity<Editor>,
 152        _subscriptions: Vec<gpui::Subscription>,
 153    },
 154    History,
 155    Configuration,
 156}
 157
 158impl ActiveView {
 159    pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {
 160        let summary = thread.read(cx).summary_or_default();
 161
 162        let editor = cx.new(|cx| {
 163            let mut editor = Editor::single_line(window, cx);
 164            editor.set_text(summary.clone(), window, cx);
 165            editor
 166        });
 167
 168        let subscriptions = vec![
 169            window.subscribe(&editor, cx, {
 170                {
 171                    let thread = thread.clone();
 172                    move |editor, event, window, cx| match event {
 173                        EditorEvent::BufferEdited => {
 174                            let new_summary = editor.read(cx).text(cx);
 175
 176                            thread.update(cx, |thread, cx| {
 177                                thread.set_summary(new_summary, cx);
 178                            })
 179                        }
 180                        EditorEvent::Blurred => {
 181                            if editor.read(cx).text(cx).is_empty() {
 182                                let summary = thread.read(cx).summary_or_default();
 183
 184                                editor.update(cx, |editor, cx| {
 185                                    editor.set_text(summary, window, cx);
 186                                });
 187                            }
 188                        }
 189                        _ => {}
 190                    }
 191                }
 192            }),
 193            window.subscribe(&thread, cx, {
 194                let editor = editor.clone();
 195                move |thread, event, window, cx| match event {
 196                    ThreadEvent::SummaryGenerated => {
 197                        let summary = thread.read(cx).summary_or_default();
 198
 199                        editor.update(cx, |editor, cx| {
 200                            editor.set_text(summary, window, cx);
 201                        })
 202                    }
 203                    _ => {}
 204                }
 205            }),
 206        ];
 207
 208        Self::Thread {
 209            change_title_editor: editor,
 210            thread: thread.downgrade(),
 211            _subscriptions: subscriptions,
 212        }
 213    }
 214
 215    pub fn prompt_editor(
 216        context_editor: Entity<ContextEditor>,
 217        window: &mut Window,
 218        cx: &mut App,
 219    ) -> Self {
 220        let title = context_editor.read(cx).title(cx).to_string();
 221
 222        let editor = cx.new(|cx| {
 223            let mut editor = Editor::single_line(window, cx);
 224            editor.set_text(title, window, cx);
 225            editor
 226        });
 227
 228        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 229        // cause a custom summary to be set. The presence of this custom summary would cause
 230        // summarization to not happen.
 231        let mut suppress_first_edit = true;
 232
 233        let subscriptions = vec![
 234            window.subscribe(&editor, cx, {
 235                {
 236                    let context_editor = context_editor.clone();
 237                    move |editor, event, window, cx| match event {
 238                        EditorEvent::BufferEdited => {
 239                            if suppress_first_edit {
 240                                suppress_first_edit = false;
 241                                return;
 242                            }
 243                            let new_summary = editor.read(cx).text(cx);
 244
 245                            context_editor.update(cx, |context_editor, cx| {
 246                                context_editor
 247                                    .context()
 248                                    .update(cx, |assistant_context, cx| {
 249                                        assistant_context.set_custom_summary(new_summary, cx);
 250                                    })
 251                            })
 252                        }
 253                        EditorEvent::Blurred => {
 254                            if editor.read(cx).text(cx).is_empty() {
 255                                let summary = context_editor
 256                                    .read(cx)
 257                                    .context()
 258                                    .read(cx)
 259                                    .summary_or_default();
 260
 261                                editor.update(cx, |editor, cx| {
 262                                    editor.set_text(summary, window, cx);
 263                                });
 264                            }
 265                        }
 266                        _ => {}
 267                    }
 268                }
 269            }),
 270            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 271                let editor = editor.clone();
 272                move |assistant_context, event, window, cx| match event {
 273                    ContextEvent::SummaryGenerated => {
 274                        let summary = assistant_context.read(cx).summary_or_default();
 275
 276                        editor.update(cx, |editor, cx| {
 277                            editor.set_text(summary, window, cx);
 278                        })
 279                    }
 280                    _ => {}
 281                }
 282            }),
 283        ];
 284
 285        Self::PromptEditor {
 286            context_editor,
 287            title_editor: editor,
 288            _subscriptions: subscriptions,
 289        }
 290    }
 291}
 292
 293pub struct AssistantPanel {
 294    workspace: WeakEntity<Workspace>,
 295    user_store: Entity<UserStore>,
 296    project: Entity<Project>,
 297    fs: Arc<dyn Fs>,
 298    language_registry: Arc<LanguageRegistry>,
 299    thread_store: Entity<ThreadStore>,
 300    thread: Entity<ActiveThread>,
 301    message_editor: Entity<MessageEditor>,
 302    _active_thread_subscriptions: Vec<Subscription>,
 303    _default_model_subscription: Subscription,
 304    context_store: Entity<assistant_context_editor::ContextStore>,
 305    prompt_store: Option<Entity<PromptStore>>,
 306    configuration: Option<Entity<AssistantConfiguration>>,
 307    configuration_subscription: Option<Subscription>,
 308    local_timezone: UtcOffset,
 309    active_view: ActiveView,
 310    previous_view: Option<ActiveView>,
 311    history_store: Entity<HistoryStore>,
 312    history: Entity<ThreadHistory>,
 313    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
 314    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
 315    assistant_navigation_menu: Option<Entity<ContextMenu>>,
 316    width: Option<Pixels>,
 317    height: Option<Pixels>,
 318    pending_serialization: Option<Task<Result<()>>>,
 319}
 320
 321impl AssistantPanel {
 322    fn serialize(&mut self, cx: &mut Context<Self>) {
 323        let width = self.width;
 324        self.pending_serialization = Some(cx.background_spawn(async move {
 325            KEY_VALUE_STORE
 326                .write_kvp(
 327                    AGENT_PANEL_KEY.into(),
 328                    serde_json::to_string(&SerializedAssistantPanel { width })?,
 329                )
 330                .await?;
 331            anyhow::Ok(())
 332        }));
 333    }
 334    pub fn load(
 335        workspace: WeakEntity<Workspace>,
 336        prompt_builder: Arc<PromptBuilder>,
 337        mut cx: AsyncWindowContext,
 338    ) -> Task<Result<Entity<Self>>> {
 339        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 340        cx.spawn(async move |cx| {
 341            let prompt_store = match prompt_store {
 342                Ok(prompt_store) => prompt_store.await.ok(),
 343                Err(_) => None,
 344            };
 345            let tools = cx.new(|_| ToolWorkingSet::default())?;
 346            let thread_store = workspace
 347                .update(cx, |workspace, cx| {
 348                    let project = workspace.project().clone();
 349                    ThreadStore::load(
 350                        project,
 351                        tools.clone(),
 352                        prompt_store.clone(),
 353                        prompt_builder.clone(),
 354                        cx,
 355                    )
 356                })?
 357                .await?;
 358
 359            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 360            let context_store = workspace
 361                .update(cx, |workspace, cx| {
 362                    let project = workspace.project().clone();
 363                    assistant_context_editor::ContextStore::new(
 364                        project,
 365                        prompt_builder.clone(),
 366                        slash_commands,
 367                        cx,
 368                    )
 369                })?
 370                .await?;
 371
 372            let serialized_panel = if let Some(panel) = cx
 373                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
 374                .await
 375                .log_err()
 376                .flatten()
 377            {
 378                Some(serde_json::from_str::<SerializedAssistantPanel>(&panel)?)
 379            } else {
 380                None
 381            };
 382
 383            let panel = workspace.update_in(cx, |workspace, window, cx| {
 384                let panel = cx.new(|cx| {
 385                    Self::new(
 386                        workspace,
 387                        thread_store,
 388                        context_store,
 389                        prompt_store,
 390                        window,
 391                        cx,
 392                    )
 393                });
 394                if let Some(serialized_panel) = serialized_panel {
 395                    panel.update(cx, |panel, cx| {
 396                        panel.width = serialized_panel.width.map(|w| w.round());
 397                        cx.notify();
 398                    });
 399                }
 400                panel
 401            })?;
 402
 403            Ok(panel)
 404        })
 405    }
 406
 407    fn new(
 408        workspace: &Workspace,
 409        thread_store: Entity<ThreadStore>,
 410        context_store: Entity<assistant_context_editor::ContextStore>,
 411        prompt_store: Option<Entity<PromptStore>>,
 412        window: &mut Window,
 413        cx: &mut Context<Self>,
 414    ) -> Self {
 415        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 416        let fs = workspace.app_state().fs.clone();
 417        let user_store = workspace.app_state().user_store.clone();
 418        let project = workspace.project();
 419        let language_registry = project.read(cx).languages().clone();
 420        let workspace = workspace.weak_handle();
 421        let weak_self = cx.entity().downgrade();
 422
 423        let message_editor_context_store = cx.new(|_cx| {
 424            crate::context_store::ContextStore::new(
 425                project.downgrade(),
 426                Some(thread_store.downgrade()),
 427            )
 428        });
 429
 430        let message_editor = cx.new(|cx| {
 431            MessageEditor::new(
 432                fs.clone(),
 433                workspace.clone(),
 434                user_store.clone(),
 435                message_editor_context_store.clone(),
 436                prompt_store.clone(),
 437                thread_store.downgrade(),
 438                thread.clone(),
 439                window,
 440                cx,
 441            )
 442        });
 443
 444        let message_editor_subscription =
 445            cx.subscribe(&message_editor, |_, _, event, cx| match event {
 446                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 447                    cx.notify();
 448                }
 449            });
 450
 451        let thread_id = thread.read(cx).id().clone();
 452        let history_store = cx.new(|cx| {
 453            HistoryStore::new(
 454                thread_store.clone(),
 455                context_store.clone(),
 456                [RecentEntry::Thread(thread_id, thread.clone())],
 457                cx,
 458            )
 459        });
 460
 461        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 462
 463        let active_view = ActiveView::thread(thread.clone(), window, cx);
 464        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 465            if let ThreadEvent::MessageAdded(_) = &event {
 466                // needed to leave empty state
 467                cx.notify();
 468            }
 469        });
 470        let active_thread = cx.new(|cx| {
 471            ActiveThread::new(
 472                thread.clone(),
 473                thread_store.clone(),
 474                message_editor_context_store.clone(),
 475                language_registry.clone(),
 476                workspace.clone(),
 477                window,
 478                cx,
 479            )
 480        });
 481        AgentDiff::set_active_thread(&workspace, &thread, window, cx);
 482
 483        let active_thread_subscription =
 484            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
 485                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 486                    cx.notify();
 487                }
 488            });
 489
 490        let weak_panel = weak_self.clone();
 491
 492        window.defer(cx, move |window, cx| {
 493            let panel = weak_panel.clone();
 494            let assistant_navigation_menu =
 495                ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 496                    let recently_opened = panel
 497                        .update(cx, |this, cx| {
 498                            this.history_store.update(cx, |history_store, cx| {
 499                                history_store.recently_opened_entries(cx)
 500                            })
 501                        })
 502                        .unwrap_or_default();
 503
 504                    if !recently_opened.is_empty() {
 505                        menu = menu.header("Recently Opened");
 506
 507                        for entry in recently_opened.iter() {
 508                            let summary = entry.summary(cx);
 509
 510                            menu = menu.entry_with_end_slot_on_hover(
 511                                summary,
 512                                None,
 513                                {
 514                                    let panel = panel.clone();
 515                                    let entry = entry.clone();
 516                                    move |window, cx| {
 517                                        panel
 518                                            .update(cx, {
 519                                                let entry = entry.clone();
 520                                                move |this, cx| match entry {
 521                                                    RecentEntry::Thread(_, thread) => {
 522                                                        this.open_thread(thread, window, cx)
 523                                                    }
 524                                                    RecentEntry::Context(context) => {
 525                                                        let Some(path) = context.read(cx).path()
 526                                                        else {
 527                                                            return;
 528                                                        };
 529                                                        this.open_saved_prompt_editor(
 530                                                            path.clone(),
 531                                                            window,
 532                                                            cx,
 533                                                        )
 534                                                        .detach_and_log_err(cx)
 535                                                    }
 536                                                }
 537                                            })
 538                                            .ok();
 539                                    }
 540                                },
 541                                IconName::Close,
 542                                "Close Entry".into(),
 543                                {
 544                                    let panel = panel.clone();
 545                                    let entry = entry.clone();
 546                                    move |_window, cx| {
 547                                        panel
 548                                            .update(cx, |this, cx| {
 549                                                this.history_store.update(
 550                                                    cx,
 551                                                    |history_store, cx| {
 552                                                        history_store.remove_recently_opened_entry(
 553                                                            &entry, cx,
 554                                                        );
 555                                                    },
 556                                                );
 557                                            })
 558                                            .ok();
 559                                    }
 560                                },
 561                            );
 562                        }
 563
 564                        menu = menu.separator();
 565                    }
 566
 567                    menu.action("View All", Box::new(OpenHistory))
 568                        .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
 569                        .fixed_width(px(320.).into())
 570                        .keep_open_on_confirm(false)
 571                        .key_context("NavigationMenu")
 572                });
 573            weak_panel
 574                .update(cx, |panel, cx| {
 575                    cx.subscribe_in(
 576                        &assistant_navigation_menu,
 577                        window,
 578                        |_, menu, _: &DismissEvent, window, cx| {
 579                            menu.update(cx, |menu, _| {
 580                                menu.clear_selected();
 581                            });
 582                            cx.focus_self(window);
 583                        },
 584                    )
 585                    .detach();
 586                    panel.assistant_navigation_menu = Some(assistant_navigation_menu);
 587                })
 588                .ok();
 589        });
 590
 591        let _default_model_subscription = cx.subscribe(
 592            &LanguageModelRegistry::global(cx),
 593            |this, _, event: &language_model::Event, cx| match event {
 594                language_model::Event::DefaultModelChanged => {
 595                    this.thread
 596                        .read(cx)
 597                        .thread()
 598                        .clone()
 599                        .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
 600                }
 601                _ => {}
 602            },
 603        );
 604
 605        Self {
 606            active_view,
 607            workspace,
 608            user_store,
 609            project: project.clone(),
 610            fs: fs.clone(),
 611            language_registry,
 612            thread_store: thread_store.clone(),
 613            thread: active_thread,
 614            message_editor,
 615            _active_thread_subscriptions: vec![
 616                thread_subscription,
 617                active_thread_subscription,
 618                message_editor_subscription,
 619            ],
 620            _default_model_subscription,
 621            context_store,
 622            prompt_store,
 623            configuration: None,
 624            configuration_subscription: None,
 625            local_timezone: UtcOffset::from_whole_seconds(
 626                chrono::Local::now().offset().local_minus_utc(),
 627            )
 628            .unwrap(),
 629            previous_view: None,
 630            history_store: history_store.clone(),
 631            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 632            assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
 633            assistant_navigation_menu_handle: PopoverMenuHandle::default(),
 634            assistant_navigation_menu: None,
 635            width: None,
 636            height: None,
 637            pending_serialization: None,
 638        }
 639    }
 640
 641    pub fn toggle_focus(
 642        workspace: &mut Workspace,
 643        _: &ToggleFocus,
 644        window: &mut Window,
 645        cx: &mut Context<Workspace>,
 646    ) {
 647        if workspace
 648            .panel::<Self>(cx)
 649            .is_some_and(|panel| panel.read(cx).enabled(cx))
 650        {
 651            workspace.toggle_panel_focus::<Self>(window, cx);
 652        }
 653    }
 654
 655    pub(crate) fn local_timezone(&self) -> UtcOffset {
 656        self.local_timezone
 657    }
 658
 659    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 660        &self.prompt_store
 661    }
 662
 663    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 664        &self.thread_store
 665    }
 666
 667    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 668        self.thread
 669            .update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 670    }
 671
 672    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 673        let thread = self
 674            .thread_store
 675            .update(cx, |this, cx| this.create_thread(cx));
 676
 677        let thread_view = ActiveView::thread(thread.clone(), window, cx);
 678        self.set_active_view(thread_view, window, cx);
 679
 680        let context_store = cx.new(|_cx| {
 681            crate::context_store::ContextStore::new(
 682                self.project.downgrade(),
 683                Some(self.thread_store.downgrade()),
 684            )
 685        });
 686
 687        if let Some(other_thread_id) = action.from_thread_id.clone() {
 688            let other_thread_task = self
 689                .thread_store
 690                .update(cx, |this, cx| this.open_thread(&other_thread_id, cx));
 691
 692            cx.spawn({
 693                let context_store = context_store.clone();
 694
 695                async move |_panel, cx| {
 696                    let other_thread = other_thread_task.await?;
 697
 698                    context_store.update(cx, |this, cx| {
 699                        this.add_thread(other_thread, false, cx);
 700                    })?;
 701                    anyhow::Ok(())
 702                }
 703            })
 704            .detach_and_log_err(cx);
 705        }
 706
 707        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 708            if let ThreadEvent::MessageAdded(_) = &event {
 709                // needed to leave empty state
 710                cx.notify();
 711            }
 712        });
 713
 714        self.thread = cx.new(|cx| {
 715            ActiveThread::new(
 716                thread.clone(),
 717                self.thread_store.clone(),
 718                context_store.clone(),
 719                self.language_registry.clone(),
 720                self.workspace.clone(),
 721                window,
 722                cx,
 723            )
 724        });
 725        AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
 726
 727        let active_thread_subscription =
 728            cx.subscribe(&self.thread, |_, _, event, cx| match &event {
 729                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 730                    cx.notify();
 731                }
 732            });
 733
 734        self.message_editor = cx.new(|cx| {
 735            MessageEditor::new(
 736                self.fs.clone(),
 737                self.workspace.clone(),
 738                self.user_store.clone(),
 739                context_store,
 740                self.prompt_store.clone(),
 741                self.thread_store.downgrade(),
 742                thread,
 743                window,
 744                cx,
 745            )
 746        });
 747        self.message_editor.focus_handle(cx).focus(window);
 748
 749        let message_editor_subscription =
 750            cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
 751                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 752                    cx.notify();
 753                }
 754            });
 755
 756        self._active_thread_subscriptions = vec![
 757            thread_subscription,
 758            active_thread_subscription,
 759            message_editor_subscription,
 760        ];
 761    }
 762
 763    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 764        let context = self
 765            .context_store
 766            .update(cx, |context_store, cx| context_store.create(cx));
 767        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 768            .log_err()
 769            .flatten();
 770
 771        let context_editor = cx.new(|cx| {
 772            let mut editor = ContextEditor::for_context(
 773                context,
 774                self.fs.clone(),
 775                self.workspace.clone(),
 776                self.project.clone(),
 777                lsp_adapter_delegate,
 778                window,
 779                cx,
 780            );
 781            editor.insert_default_prompt(window, cx);
 782            editor
 783        });
 784
 785        self.set_active_view(
 786            ActiveView::prompt_editor(context_editor.clone(), window, cx),
 787            window,
 788            cx,
 789        );
 790        context_editor.focus_handle(cx).focus(window);
 791    }
 792
 793    fn deploy_rules_library(
 794        &mut self,
 795        action: &OpenRulesLibrary,
 796        _window: &mut Window,
 797        cx: &mut Context<Self>,
 798    ) {
 799        open_rules_library(
 800            self.language_registry.clone(),
 801            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
 802            Arc::new(|| {
 803                Box::new(SlashCommandCompletionProvider::new(
 804                    Arc::new(SlashCommandWorkingSet::default()),
 805                    None,
 806                    None,
 807                ))
 808            }),
 809            action
 810                .prompt_to_select
 811                .map(|uuid| UserPromptId(uuid).into()),
 812            cx,
 813        )
 814        .detach_and_log_err(cx);
 815    }
 816
 817    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 818        if matches!(self.active_view, ActiveView::History) {
 819            if let Some(previous_view) = self.previous_view.take() {
 820                self.set_active_view(previous_view, window, cx);
 821            }
 822        } else {
 823            self.thread_store
 824                .update(cx, |thread_store, cx| thread_store.reload(cx))
 825                .detach_and_log_err(cx);
 826            self.set_active_view(ActiveView::History, window, cx);
 827        }
 828        cx.notify();
 829    }
 830
 831    pub(crate) fn open_saved_prompt_editor(
 832        &mut self,
 833        path: Arc<Path>,
 834        window: &mut Window,
 835        cx: &mut Context<Self>,
 836    ) -> Task<Result<()>> {
 837        let context = self
 838            .context_store
 839            .update(cx, |store, cx| store.open_local_context(path, cx));
 840        let fs = self.fs.clone();
 841        let project = self.project.clone();
 842        let workspace = self.workspace.clone();
 843
 844        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 845
 846        cx.spawn_in(window, async move |this, cx| {
 847            let context = context.await?;
 848            this.update_in(cx, |this, window, cx| {
 849                let editor = cx.new(|cx| {
 850                    ContextEditor::for_context(
 851                        context,
 852                        fs,
 853                        workspace,
 854                        project,
 855                        lsp_adapter_delegate,
 856                        window,
 857                        cx,
 858                    )
 859                });
 860
 861                this.set_active_view(
 862                    ActiveView::prompt_editor(editor.clone(), window, cx),
 863                    window,
 864                    cx,
 865                );
 866
 867                anyhow::Ok(())
 868            })??;
 869            Ok(())
 870        })
 871    }
 872
 873    pub(crate) fn open_thread_by_id(
 874        &mut self,
 875        thread_id: &ThreadId,
 876        window: &mut Window,
 877        cx: &mut Context<Self>,
 878    ) -> Task<Result<()>> {
 879        let open_thread_task = self
 880            .thread_store
 881            .update(cx, |this, cx| this.open_thread(thread_id, cx));
 882        cx.spawn_in(window, async move |this, cx| {
 883            let thread = open_thread_task.await?;
 884            this.update_in(cx, |this, window, cx| {
 885                this.open_thread(thread, window, cx);
 886                anyhow::Ok(())
 887            })??;
 888            Ok(())
 889        })
 890    }
 891
 892    pub(crate) fn open_thread(
 893        &mut self,
 894        thread: Entity<Thread>,
 895        window: &mut Window,
 896        cx: &mut Context<Self>,
 897    ) {
 898        let thread_view = ActiveView::thread(thread.clone(), window, cx);
 899        self.set_active_view(thread_view, window, cx);
 900        let context_store = cx.new(|_cx| {
 901            crate::context_store::ContextStore::new(
 902                self.project.downgrade(),
 903                Some(self.thread_store.downgrade()),
 904            )
 905        });
 906        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 907            if let ThreadEvent::MessageAdded(_) = &event {
 908                // needed to leave empty state
 909                cx.notify();
 910            }
 911        });
 912
 913        self.thread = cx.new(|cx| {
 914            ActiveThread::new(
 915                thread.clone(),
 916                self.thread_store.clone(),
 917                context_store.clone(),
 918                self.language_registry.clone(),
 919                self.workspace.clone(),
 920                window,
 921                cx,
 922            )
 923        });
 924        AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
 925
 926        let active_thread_subscription =
 927            cx.subscribe(&self.thread, |_, _, event, cx| match &event {
 928                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 929                    cx.notify();
 930                }
 931            });
 932
 933        self.message_editor = cx.new(|cx| {
 934            MessageEditor::new(
 935                self.fs.clone(),
 936                self.workspace.clone(),
 937                self.user_store.clone(),
 938                context_store,
 939                self.prompt_store.clone(),
 940                self.thread_store.downgrade(),
 941                thread,
 942                window,
 943                cx,
 944            )
 945        });
 946        self.message_editor.focus_handle(cx).focus(window);
 947
 948        let message_editor_subscription =
 949            cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
 950                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 951                    cx.notify();
 952                }
 953            });
 954
 955        self._active_thread_subscriptions = vec![
 956            thread_subscription,
 957            active_thread_subscription,
 958            message_editor_subscription,
 959        ];
 960    }
 961
 962    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
 963        match self.active_view {
 964            ActiveView::Configuration | ActiveView::History => {
 965                self.active_view =
 966                    ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
 967                self.message_editor.focus_handle(cx).focus(window);
 968                cx.notify();
 969            }
 970            _ => {}
 971        }
 972    }
 973
 974    pub fn toggle_navigation_menu(
 975        &mut self,
 976        _: &ToggleNavigationMenu,
 977        window: &mut Window,
 978        cx: &mut Context<Self>,
 979    ) {
 980        self.assistant_navigation_menu_handle.toggle(window, cx);
 981    }
 982
 983    pub fn toggle_options_menu(
 984        &mut self,
 985        _: &ToggleOptionsMenu,
 986        window: &mut Window,
 987        cx: &mut Context<Self>,
 988    ) {
 989        self.assistant_dropdown_menu_handle.toggle(window, cx);
 990    }
 991
 992    pub fn open_agent_diff(
 993        &mut self,
 994        _: &OpenAgentDiff,
 995        window: &mut Window,
 996        cx: &mut Context<Self>,
 997    ) {
 998        let thread = self.thread.read(cx).thread().clone();
 999        self.workspace
1000            .update(cx, |workspace, cx| {
1001                AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx)
1002            })
1003            .log_err();
1004    }
1005
1006    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1007        let context_server_manager = self.thread_store.read(cx).context_server_manager();
1008        let tools = self.thread_store.read(cx).tools();
1009        let fs = self.fs.clone();
1010
1011        self.set_active_view(ActiveView::Configuration, window, cx);
1012        self.configuration =
1013            Some(cx.new(|cx| {
1014                AssistantConfiguration::new(fs, context_server_manager, tools, window, cx)
1015            }));
1016
1017        if let Some(configuration) = self.configuration.as_ref() {
1018            self.configuration_subscription = Some(cx.subscribe_in(
1019                configuration,
1020                window,
1021                Self::handle_assistant_configuration_event,
1022            ));
1023
1024            configuration.focus_handle(cx).focus(window);
1025        }
1026    }
1027
1028    pub(crate) fn open_active_thread_as_markdown(
1029        &mut self,
1030        _: &OpenActiveThreadAsMarkdown,
1031        window: &mut Window,
1032        cx: &mut Context<Self>,
1033    ) {
1034        let Some(workspace) = self
1035            .workspace
1036            .upgrade()
1037            .ok_or_else(|| anyhow!("workspace dropped"))
1038            .log_err()
1039        else {
1040            return;
1041        };
1042
1043        let markdown_language_task = workspace
1044            .read(cx)
1045            .app_state()
1046            .languages
1047            .language_for_name("Markdown");
1048        let thread = self.active_thread(cx);
1049        cx.spawn_in(window, async move |_this, cx| {
1050            let markdown_language = markdown_language_task.await?;
1051
1052            workspace.update_in(cx, |workspace, window, cx| {
1053                let thread = thread.read(cx);
1054                let markdown = thread.to_markdown(cx)?;
1055                let thread_summary = thread
1056                    .summary()
1057                    .map(|summary| summary.to_string())
1058                    .unwrap_or_else(|| "Thread".to_string());
1059
1060                let project = workspace.project().clone();
1061                let buffer = project.update(cx, |project, cx| {
1062                    project.create_local_buffer(&markdown, Some(markdown_language), cx)
1063                });
1064                let buffer = cx.new(|cx| {
1065                    MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone())
1066                });
1067
1068                workspace.add_item_to_active_pane(
1069                    Box::new(cx.new(|cx| {
1070                        let mut editor =
1071                            Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
1072                        editor.set_breadcrumb_header(thread_summary);
1073                        editor
1074                    })),
1075                    None,
1076                    true,
1077                    window,
1078                    cx,
1079                );
1080
1081                anyhow::Ok(())
1082            })
1083        })
1084        .detach_and_log_err(cx);
1085    }
1086
1087    fn handle_assistant_configuration_event(
1088        &mut self,
1089        _entity: &Entity<AssistantConfiguration>,
1090        event: &AssistantConfigurationEvent,
1091        window: &mut Window,
1092        cx: &mut Context<Self>,
1093    ) {
1094        match event {
1095            AssistantConfigurationEvent::NewThread(provider) => {
1096                if LanguageModelRegistry::read_global(cx)
1097                    .default_model()
1098                    .map_or(true, |model| model.provider.id() != provider.id())
1099                {
1100                    if let Some(model) = provider.default_model(cx) {
1101                        update_settings_file::<AssistantSettings>(
1102                            self.fs.clone(),
1103                            cx,
1104                            move |settings, _| settings.set_model(model),
1105                        );
1106                    }
1107                }
1108
1109                self.new_thread(&NewThread::default(), window, cx);
1110            }
1111        }
1112    }
1113
1114    pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
1115        self.thread.read(cx).thread().clone()
1116    }
1117
1118    pub(crate) fn delete_thread(
1119        &mut self,
1120        thread_id: &ThreadId,
1121        cx: &mut Context<Self>,
1122    ) -> Task<Result<()>> {
1123        self.thread_store
1124            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1125    }
1126
1127    pub(crate) fn has_active_thread(&self) -> bool {
1128        matches!(self.active_view, ActiveView::Thread { .. })
1129    }
1130
1131    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
1132        match &self.active_view {
1133            ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
1134            _ => None,
1135        }
1136    }
1137
1138    pub(crate) fn delete_context(
1139        &mut self,
1140        path: Arc<Path>,
1141        cx: &mut Context<Self>,
1142    ) -> Task<Result<()>> {
1143        self.context_store
1144            .update(cx, |this, cx| this.delete_local_context(path, cx))
1145    }
1146
1147    fn set_active_view(
1148        &mut self,
1149        new_view: ActiveView,
1150        window: &mut Window,
1151        cx: &mut Context<Self>,
1152    ) {
1153        let current_is_history = matches!(self.active_view, ActiveView::History);
1154        let new_is_history = matches!(new_view, ActiveView::History);
1155
1156        match &self.active_view {
1157            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1158                if let Some(thread) = thread.upgrade() {
1159                    if thread.read(cx).is_empty() {
1160                        let id = thread.read(cx).id().clone();
1161                        store.remove_recently_opened_thread(id, cx);
1162                    }
1163                }
1164            }),
1165            _ => {}
1166        }
1167
1168        match &new_view {
1169            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1170                if let Some(thread) = thread.upgrade() {
1171                    let id = thread.read(cx).id().clone();
1172                    store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
1173                }
1174            }),
1175            ActiveView::PromptEditor { context_editor, .. } => {
1176                self.history_store.update(cx, |store, cx| {
1177                    let context = context_editor.read(cx).context().clone();
1178                    store.push_recently_opened_entry(RecentEntry::Context(context), cx)
1179                })
1180            }
1181            _ => {}
1182        }
1183
1184        if current_is_history && !new_is_history {
1185            self.active_view = new_view;
1186        } else if !current_is_history && new_is_history {
1187            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1188        } else {
1189            if !new_is_history {
1190                self.previous_view = None;
1191            }
1192            self.active_view = new_view;
1193        }
1194
1195        self.focus_handle(cx).focus(window);
1196    }
1197}
1198
1199impl Focusable for AssistantPanel {
1200    fn focus_handle(&self, cx: &App) -> FocusHandle {
1201        match &self.active_view {
1202            ActiveView::Thread { .. } => self.message_editor.focus_handle(cx),
1203            ActiveView::History => self.history.focus_handle(cx),
1204            ActiveView::PromptEditor { context_editor, .. } => context_editor.focus_handle(cx),
1205            ActiveView::Configuration => {
1206                if let Some(configuration) = self.configuration.as_ref() {
1207                    configuration.focus_handle(cx)
1208                } else {
1209                    cx.focus_handle()
1210                }
1211            }
1212        }
1213    }
1214}
1215
1216impl EventEmitter<PanelEvent> for AssistantPanel {}
1217
1218impl Panel for AssistantPanel {
1219    fn persistent_name() -> &'static str {
1220        "AgentPanel"
1221    }
1222
1223    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1224        match AssistantSettings::get_global(cx).dock {
1225            AssistantDockPosition::Left => DockPosition::Left,
1226            AssistantDockPosition::Bottom => DockPosition::Bottom,
1227            AssistantDockPosition::Right => DockPosition::Right,
1228        }
1229    }
1230
1231    fn position_is_valid(&self, _: DockPosition) -> bool {
1232        true
1233    }
1234
1235    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1236        settings::update_settings_file::<AssistantSettings>(
1237            self.fs.clone(),
1238            cx,
1239            move |settings, _| {
1240                let dock = match position {
1241                    DockPosition::Left => AssistantDockPosition::Left,
1242                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1243                    DockPosition::Right => AssistantDockPosition::Right,
1244                };
1245                settings.set_dock(dock);
1246            },
1247        );
1248    }
1249
1250    fn size(&self, window: &Window, cx: &App) -> Pixels {
1251        let settings = AssistantSettings::get_global(cx);
1252        match self.position(window, cx) {
1253            DockPosition::Left | DockPosition::Right => {
1254                self.width.unwrap_or(settings.default_width)
1255            }
1256            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1257        }
1258    }
1259
1260    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1261        match self.position(window, cx) {
1262            DockPosition::Left | DockPosition::Right => self.width = size,
1263            DockPosition::Bottom => self.height = size,
1264        }
1265        self.serialize(cx);
1266        cx.notify();
1267    }
1268
1269    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1270
1271    fn remote_id() -> Option<proto::PanelId> {
1272        Some(proto::PanelId::AssistantPanel)
1273    }
1274
1275    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1276        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
1277            .then_some(IconName::ZedAssistant)
1278    }
1279
1280    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1281        Some("Agent Panel")
1282    }
1283
1284    fn toggle_action(&self) -> Box<dyn Action> {
1285        Box::new(ToggleFocus)
1286    }
1287
1288    fn activation_priority(&self) -> u32 {
1289        3
1290    }
1291
1292    fn enabled(&self, cx: &App) -> bool {
1293        AssistantSettings::get_global(cx).enabled
1294    }
1295}
1296
1297impl AssistantPanel {
1298    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1299        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1300
1301        let content = match &self.active_view {
1302            ActiveView::Thread {
1303                change_title_editor,
1304                ..
1305            } => {
1306                let active_thread = self.thread.read(cx);
1307                let is_empty = active_thread.is_empty();
1308
1309                let summary = active_thread.summary(cx);
1310
1311                if is_empty {
1312                    Label::new(Thread::DEFAULT_SUMMARY.clone())
1313                        .truncate()
1314                        .into_any_element()
1315                } else if summary.is_none() {
1316                    Label::new(LOADING_SUMMARY_PLACEHOLDER)
1317                        .truncate()
1318                        .into_any_element()
1319                } else {
1320                    div()
1321                        .w_full()
1322                        .child(change_title_editor.clone())
1323                        .into_any_element()
1324                }
1325            }
1326            ActiveView::PromptEditor {
1327                title_editor,
1328                context_editor,
1329                ..
1330            } => {
1331                let context_editor = context_editor.read(cx);
1332                let summary = context_editor.context().read(cx).summary();
1333
1334                match summary {
1335                    None => Label::new(AssistantContext::DEFAULT_SUMMARY.clone())
1336                        .truncate()
1337                        .into_any_element(),
1338                    Some(summary) => {
1339                        if summary.done {
1340                            div()
1341                                .w_full()
1342                                .child(title_editor.clone())
1343                                .into_any_element()
1344                        } else {
1345                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1346                                .truncate()
1347                                .into_any_element()
1348                        }
1349                    }
1350                }
1351            }
1352            ActiveView::History => Label::new("History").truncate().into_any_element(),
1353            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1354        };
1355
1356        h_flex()
1357            .key_context("TitleEditor")
1358            .id("TitleEditor")
1359            .flex_grow()
1360            .w_full()
1361            .max_w_full()
1362            .overflow_x_scroll()
1363            .child(content)
1364            .into_any()
1365    }
1366
1367    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1368        let active_thread = self.thread.read(cx);
1369        let thread = active_thread.thread().read(cx);
1370        let thread_id = thread.id().clone();
1371        let is_empty = active_thread.is_empty();
1372
1373        let show_token_count = match &self.active_view {
1374            ActiveView::Thread { .. } => !is_empty,
1375            ActiveView::PromptEditor { .. } => true,
1376            _ => false,
1377        };
1378
1379        let focus_handle = self.focus_handle(cx);
1380
1381        let go_back_button = div().child(
1382            IconButton::new("go-back", IconName::ArrowLeft)
1383                .icon_size(IconSize::Small)
1384                .on_click(cx.listener(|this, _, window, cx| {
1385                    this.go_back(&workspace::GoBack, window, cx);
1386                }))
1387                .tooltip({
1388                    let focus_handle = focus_handle.clone();
1389                    move |window, cx| {
1390                        Tooltip::for_action_in(
1391                            "Go Back",
1392                            &workspace::GoBack,
1393                            &focus_handle,
1394                            window,
1395                            cx,
1396                        )
1397                    }
1398                }),
1399        );
1400
1401        let recent_entries_menu = div().child(
1402            PopoverMenu::new("agent-nav-menu")
1403                .trigger_with_tooltip(
1404                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
1405                        .icon_size(IconSize::Small)
1406                        .style(ui::ButtonStyle::Subtle),
1407                    {
1408                        let focus_handle = focus_handle.clone();
1409                        move |window, cx| {
1410                            Tooltip::for_action_in(
1411                                "Toggle Panel Menu",
1412                                &ToggleNavigationMenu,
1413                                &focus_handle,
1414                                window,
1415                                cx,
1416                            )
1417                        }
1418                    },
1419                )
1420                .anchor(Corner::TopLeft)
1421                .with_handle(self.assistant_navigation_menu_handle.clone())
1422                .menu({
1423                    let menu = self.assistant_navigation_menu.clone();
1424                    move |window, cx| {
1425                        if let Some(menu) = menu.as_ref() {
1426                            menu.update(cx, |_, cx| {
1427                                cx.defer_in(window, |menu, window, cx| {
1428                                    menu.rebuild(window, cx);
1429                                });
1430                            })
1431                        }
1432                        menu.clone()
1433                    }
1434                }),
1435        );
1436
1437        let agent_extra_menu = PopoverMenu::new("agent-options-menu")
1438            .trigger_with_tooltip(
1439                IconButton::new("agent-options-menu", IconName::Ellipsis)
1440                    .icon_size(IconSize::Small),
1441                {
1442                    let focus_handle = focus_handle.clone();
1443                    move |window, cx| {
1444                        Tooltip::for_action_in(
1445                            "Toggle Agent Menu",
1446                            &ToggleOptionsMenu,
1447                            &focus_handle,
1448                            window,
1449                            cx,
1450                        )
1451                    }
1452                },
1453            )
1454            .anchor(Corner::TopRight)
1455            .with_handle(self.assistant_dropdown_menu_handle.clone())
1456            .menu(move |window, cx| {
1457                Some(ContextMenu::build(window, cx, |menu, _window, _cx| {
1458                    menu.when(!is_empty, |menu| {
1459                        menu.action(
1460                            "Start New From Summary",
1461                            Box::new(NewThread {
1462                                from_thread_id: Some(thread_id.clone()),
1463                            }),
1464                        )
1465                        .separator()
1466                    })
1467                    .action("New Text Thread", NewTextThread.boxed_clone())
1468                    .action("Rules Library", Box::new(OpenRulesLibrary::default()))
1469                    .action("Settings", Box::new(OpenConfiguration))
1470                    .separator()
1471                    .header("MCPs")
1472                    .action(
1473                        "View Server Extensions",
1474                        Box::new(zed_actions::Extensions {
1475                            category_filter: Some(
1476                                zed_actions::ExtensionCategoryFilter::ContextServers,
1477                            ),
1478                        }),
1479                    )
1480                    .action("Add Custom Server", Box::new(AddContextServer))
1481                }))
1482            });
1483
1484        h_flex()
1485            .id("assistant-toolbar")
1486            .h(Tab::container_height(cx))
1487            .max_w_full()
1488            .flex_none()
1489            .justify_between()
1490            .gap_2()
1491            .bg(cx.theme().colors().tab_bar_background)
1492            .border_b_1()
1493            .border_color(cx.theme().colors().border)
1494            .child(
1495                h_flex()
1496                    .size_full()
1497                    .pl_1()
1498                    .gap_1()
1499                    .child(match &self.active_view {
1500                        ActiveView::History | ActiveView::Configuration => go_back_button,
1501                        _ => recent_entries_menu,
1502                    })
1503                    .child(self.render_title_view(window, cx)),
1504            )
1505            .child(
1506                h_flex()
1507                    .h_full()
1508                    .gap_2()
1509                    .when(show_token_count, |parent| {
1510                        parent.children(self.render_token_count(&thread, cx))
1511                    })
1512                    .child(
1513                        h_flex()
1514                            .h_full()
1515                            .gap(DynamicSpacing::Base02.rems(cx))
1516                            .px(DynamicSpacing::Base08.rems(cx))
1517                            .border_l_1()
1518                            .border_color(cx.theme().colors().border)
1519                            .child(
1520                                IconButton::new("new", IconName::Plus)
1521                                    .icon_size(IconSize::Small)
1522                                    .style(ButtonStyle::Subtle)
1523                                    .tooltip(move |window, cx| {
1524                                        Tooltip::for_action_in(
1525                                            "New Thread",
1526                                            &NewThread::default(),
1527                                            &focus_handle,
1528                                            window,
1529                                            cx,
1530                                        )
1531                                    })
1532                                    .on_click(move |_event, window, cx| {
1533                                        window.dispatch_action(
1534                                            NewThread::default().boxed_clone(),
1535                                            cx,
1536                                        );
1537                                    }),
1538                            )
1539                            .child(agent_extra_menu),
1540                    ),
1541            )
1542    }
1543
1544    fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
1545        let is_generating = thread.is_generating();
1546        let message_editor = self.message_editor.read(cx);
1547
1548        let conversation_token_usage = thread.total_token_usage()?;
1549
1550        let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
1551            self.thread.read(cx).editing_message_id()
1552        {
1553            let combined = thread
1554                .token_usage_up_to_message(editing_message_id)
1555                .add(unsent_tokens);
1556
1557            (combined, unsent_tokens > 0)
1558        } else {
1559            let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
1560            let combined = conversation_token_usage.add(unsent_tokens);
1561
1562            (combined, unsent_tokens > 0)
1563        };
1564
1565        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
1566
1567        match &self.active_view {
1568            ActiveView::Thread { .. } => {
1569                if total_token_usage.total == 0 {
1570                    return None;
1571                }
1572
1573                let token_color = match total_token_usage.ratio() {
1574                    TokenUsageRatio::Normal if is_estimating => Color::Default,
1575                    TokenUsageRatio::Normal => Color::Muted,
1576                    TokenUsageRatio::Warning => Color::Warning,
1577                    TokenUsageRatio::Exceeded => Color::Error,
1578                };
1579
1580                let token_count = h_flex()
1581                    .id("token-count")
1582                    .flex_shrink_0()
1583                    .gap_0p5()
1584                    .when(!is_generating && is_estimating, |parent| {
1585                        parent
1586                            .child(
1587                                h_flex()
1588                                    .mr_1()
1589                                    .size_2p5()
1590                                    .justify_center()
1591                                    .rounded_full()
1592                                    .bg(cx.theme().colors().text.opacity(0.1))
1593                                    .child(
1594                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
1595                                    ),
1596                            )
1597                            .tooltip(move |window, cx| {
1598                                Tooltip::with_meta(
1599                                    "Estimated New Token Count",
1600                                    None,
1601                                    format!(
1602                                        "Current Conversation Tokens: {}",
1603                                        humanize_token_count(conversation_token_usage.total)
1604                                    ),
1605                                    window,
1606                                    cx,
1607                                )
1608                            })
1609                    })
1610                    .child(
1611                        Label::new(humanize_token_count(total_token_usage.total))
1612                            .size(LabelSize::Small)
1613                            .color(token_color)
1614                            .map(|label| {
1615                                if is_generating || is_waiting_to_update_token_count {
1616                                    label
1617                                        .with_animation(
1618                                            "used-tokens-label",
1619                                            Animation::new(Duration::from_secs(2))
1620                                                .repeat()
1621                                                .with_easing(pulsating_between(0.6, 1.)),
1622                                            |label, delta| label.alpha(delta),
1623                                        )
1624                                        .into_any()
1625                                } else {
1626                                    label.into_any_element()
1627                                }
1628                            }),
1629                    )
1630                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
1631                    .child(
1632                        Label::new(humanize_token_count(total_token_usage.max))
1633                            .size(LabelSize::Small)
1634                            .color(Color::Muted),
1635                    )
1636                    .into_any();
1637
1638                Some(token_count)
1639            }
1640            ActiveView::PromptEditor { context_editor, .. } => {
1641                let element = render_remaining_tokens(context_editor, cx)?;
1642
1643                Some(element.into_any_element())
1644            }
1645            _ => None,
1646        }
1647    }
1648
1649    fn render_active_thread_or_empty_state(
1650        &self,
1651        window: &mut Window,
1652        cx: &mut Context<Self>,
1653    ) -> AnyElement {
1654        if self.thread.read(cx).is_empty() {
1655            return self
1656                .render_thread_empty_state(window, cx)
1657                .into_any_element();
1658        }
1659
1660        self.thread.clone().into_any_element()
1661    }
1662
1663    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
1664        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
1665            return Some(ConfigurationError::NoProvider);
1666        };
1667
1668        if !model.provider.is_authenticated(cx) {
1669            return Some(ConfigurationError::ProviderNotAuthenticated);
1670        }
1671
1672        if model.provider.must_accept_terms(cx) {
1673            return Some(ConfigurationError::ProviderPendingTermsAcceptance(
1674                model.provider,
1675            ));
1676        }
1677
1678        None
1679    }
1680
1681    fn render_thread_empty_state(
1682        &self,
1683        window: &mut Window,
1684        cx: &mut Context<Self>,
1685    ) -> impl IntoElement {
1686        let recent_history = self
1687            .history_store
1688            .update(cx, |this, cx| this.recent_entries(6, cx));
1689
1690        let configuration_error = self.configuration_error(cx);
1691        let no_error = configuration_error.is_none();
1692        let focus_handle = self.focus_handle(cx);
1693
1694        v_flex()
1695            .size_full()
1696            .when(recent_history.is_empty(), |this| {
1697                let configuration_error_ref = &configuration_error;
1698                this.child(
1699                    v_flex()
1700                        .size_full()
1701                        .max_w_80()
1702                        .mx_auto()
1703                        .justify_center()
1704                        .items_center()
1705                        .gap_1()
1706                        .child(
1707                            h_flex().child(
1708                                Headline::new("Welcome to the Agent Panel")
1709                            ),
1710                        )
1711                        .when(no_error, |parent| {
1712                            parent
1713                                .child(
1714                                    h_flex().child(
1715                                        Label::new("Ask and build anything.")
1716                                            .color(Color::Muted)
1717                                            .mb_2p5(),
1718                                    ),
1719                                )
1720                                .child(
1721                                    Button::new("new-thread", "Start New Thread")
1722                                        .icon(IconName::Plus)
1723                                        .icon_position(IconPosition::Start)
1724                                        .icon_size(IconSize::Small)
1725                                        .icon_color(Color::Muted)
1726                                        .full_width()
1727                                        .key_binding(KeyBinding::for_action_in(
1728                                            &NewThread::default(),
1729                                            &focus_handle,
1730                                            window,
1731                                            cx,
1732                                        ))
1733                                        .on_click(|_event, window, cx| {
1734                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
1735                                        }),
1736                                )
1737                                .child(
1738                                    Button::new("context", "Add Context")
1739                                        .icon(IconName::FileCode)
1740                                        .icon_position(IconPosition::Start)
1741                                        .icon_size(IconSize::Small)
1742                                        .icon_color(Color::Muted)
1743                                        .full_width()
1744                                        .key_binding(KeyBinding::for_action_in(
1745                                            &ToggleContextPicker,
1746                                            &focus_handle,
1747                                            window,
1748                                            cx,
1749                                        ))
1750                                        .on_click(|_event, window, cx| {
1751                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
1752                                        }),
1753                                )
1754                                .child(
1755                                    Button::new("mode", "Switch Model")
1756                                        .icon(IconName::DatabaseZap)
1757                                        .icon_position(IconPosition::Start)
1758                                        .icon_size(IconSize::Small)
1759                                        .icon_color(Color::Muted)
1760                                        .full_width()
1761                                        .key_binding(KeyBinding::for_action_in(
1762                                            &ToggleModelSelector,
1763                                            &focus_handle,
1764                                            window,
1765                                            cx,
1766                                        ))
1767                                        .on_click(|_event, window, cx| {
1768                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
1769                                        }),
1770                                )
1771                                .child(
1772                                    Button::new("settings", "View Settings")
1773                                        .icon(IconName::Settings)
1774                                        .icon_position(IconPosition::Start)
1775                                        .icon_size(IconSize::Small)
1776                                        .icon_color(Color::Muted)
1777                                        .full_width()
1778                                        .key_binding(KeyBinding::for_action_in(
1779                                            &OpenConfiguration,
1780                                            &focus_handle,
1781                                            window,
1782                                            cx,
1783                                        ))
1784                                        .on_click(|_event, window, cx| {
1785                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1786                                        }),
1787                                )
1788                        })
1789                        .map(|parent| {
1790                            match configuration_error_ref {
1791                                Some(ConfigurationError::ProviderNotAuthenticated)
1792                                | Some(ConfigurationError::NoProvider) => {
1793                                    parent
1794                                        .child(
1795                                            h_flex().child(
1796                                                Label::new("To start using the agent, configure at least one LLM provider.")
1797                                                    .color(Color::Muted)
1798                                                    .mb_2p5()
1799                                            )
1800                                        )
1801                                        .child(
1802                                            Button::new("settings", "Configure a Provider")
1803                                                .icon(IconName::Settings)
1804                                                .icon_position(IconPosition::Start)
1805                                                .icon_size(IconSize::Small)
1806                                                .icon_color(Color::Muted)
1807                                                .full_width()
1808                                                .key_binding(KeyBinding::for_action_in(
1809                                                    &OpenConfiguration,
1810                                                    &focus_handle,
1811                                                    window,
1812                                                    cx,
1813                                                ))
1814                                                .on_click(|_event, window, cx| {
1815                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1816                                                }),
1817                                        )
1818                                }
1819                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1820                                    parent.children(
1821                                        provider.render_accept_terms(
1822                                            LanguageModelProviderTosView::ThreadFreshStart,
1823                                            cx,
1824                                        ),
1825                                    )
1826                                }
1827                                None => parent,
1828                            }
1829                        })
1830                )
1831            })
1832            .when(!recent_history.is_empty(), |parent| {
1833                let focus_handle = focus_handle.clone();
1834                let configuration_error_ref = &configuration_error;
1835
1836                parent
1837                    .overflow_hidden()
1838                    .p_1p5()
1839                    .justify_end()
1840                    .gap_1()
1841                    .child(
1842                        h_flex()
1843                            .pl_1p5()
1844                            .pb_1()
1845                            .w_full()
1846                            .justify_between()
1847                            .border_b_1()
1848                            .border_color(cx.theme().colors().border_variant)
1849                            .child(
1850                                Label::new("Past Interactions")
1851                                    .size(LabelSize::Small)
1852                                    .color(Color::Muted),
1853                            )
1854                            .child(
1855                                Button::new("view-history", "View All")
1856                                    .style(ButtonStyle::Subtle)
1857                                    .label_size(LabelSize::Small)
1858                                    .key_binding(
1859                                        KeyBinding::for_action_in(
1860                                            &OpenHistory,
1861                                            &self.focus_handle(cx),
1862                                            window,
1863                                            cx,
1864                                        ).map(|kb| kb.size(rems_from_px(12.))),
1865                                    )
1866                                    .on_click(move |_event, window, cx| {
1867                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1868                                    }),
1869                            ),
1870                    )
1871                    .child(
1872                        v_flex()
1873                            .gap_1()
1874                            .children(
1875                                recent_history.into_iter().map(|entry| {
1876                                    // TODO: Add keyboard navigation.
1877                                    match entry {
1878                                        HistoryEntry::Thread(thread) => {
1879                                            PastThread::new(thread, cx.entity().downgrade(), false, vec![])
1880                                                .into_any_element()
1881                                        }
1882                                        HistoryEntry::Context(context) => {
1883                                            PastContext::new(context, cx.entity().downgrade(), false, vec![])
1884                                                .into_any_element()
1885                                        }
1886                                    }
1887                                }),
1888                            )
1889                    )
1890                    .map(|parent| {
1891                        match configuration_error_ref {
1892                            Some(ConfigurationError::ProviderNotAuthenticated)
1893                            | Some(ConfigurationError::NoProvider) => {
1894                                parent
1895                                    .child(
1896                                        Banner::new()
1897                                            .severity(ui::Severity::Warning)
1898                                            .child(
1899                                                Label::new(
1900                                                    "Configure at least one LLM provider to start using the panel.",
1901                                                )
1902                                                .size(LabelSize::Small),
1903                                            )
1904                                            .action_slot(
1905                                                Button::new("settings", "Configure Provider")
1906                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
1907                                                    .label_size(LabelSize::Small)
1908                                                    .key_binding(
1909                                                        KeyBinding::for_action_in(
1910                                                            &OpenConfiguration,
1911                                                            &focus_handle,
1912                                                            window,
1913                                                            cx,
1914                                                        )
1915                                                        .map(|kb| kb.size(rems_from_px(12.))),
1916                                                    )
1917                                                    .on_click(|_event, window, cx| {
1918                                                        window.dispatch_action(
1919                                                            OpenConfiguration.boxed_clone(),
1920                                                            cx,
1921                                                        )
1922                                                    }),
1923                                            ),
1924                                    )
1925                            }
1926                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1927                                parent
1928                                    .child(
1929                                        Banner::new()
1930                                            .severity(ui::Severity::Warning)
1931                                            .child(
1932                                                h_flex()
1933                                                    .w_full()
1934                                                    .children(
1935                                                        provider.render_accept_terms(
1936                                                            LanguageModelProviderTosView::ThreadtEmptyState,
1937                                                            cx,
1938                                                        ),
1939                                                    ),
1940                                            ),
1941                                    )
1942                            }
1943                            None => parent,
1944                        }
1945                    })
1946            })
1947    }
1948
1949    fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1950        let tool_use_limit_reached = self
1951            .thread
1952            .read(cx)
1953            .thread()
1954            .read(cx)
1955            .tool_use_limit_reached();
1956        if !tool_use_limit_reached {
1957            return None;
1958        }
1959
1960        let model = self
1961            .thread
1962            .read(cx)
1963            .thread()
1964            .read(cx)
1965            .configured_model()?
1966            .model;
1967
1968        let max_mode_upsell = if model.supports_max_mode() {
1969            " Enable max mode for unlimited tool use."
1970        } else {
1971            ""
1972        };
1973
1974        Some(
1975            Banner::new()
1976                .severity(ui::Severity::Info)
1977                .child(h_flex().child(Label::new(format!(
1978                    "Consecutive tool use limit reached.{max_mode_upsell}"
1979                ))))
1980                .into_any_element(),
1981        )
1982    }
1983
1984    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1985        let last_error = self.thread.read(cx).last_error()?;
1986
1987        Some(
1988            div()
1989                .absolute()
1990                .right_3()
1991                .bottom_12()
1992                .max_w_96()
1993                .py_2()
1994                .px_3()
1995                .elevation_2(cx)
1996                .occlude()
1997                .child(match last_error {
1998                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
1999                    ThreadError::MaxMonthlySpendReached => {
2000                        self.render_max_monthly_spend_reached_error(cx)
2001                    }
2002                    ThreadError::ModelRequestLimitReached { plan } => {
2003                        self.render_model_request_limit_reached_error(plan, cx)
2004                    }
2005                    ThreadError::Message { header, message } => {
2006                        self.render_error_message(header, message, cx)
2007                    }
2008                })
2009                .into_any(),
2010        )
2011    }
2012
2013    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
2014        const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
2015
2016        v_flex()
2017            .gap_0p5()
2018            .child(
2019                h_flex()
2020                    .gap_1p5()
2021                    .items_center()
2022                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2023                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
2024            )
2025            .child(
2026                div()
2027                    .id("error-message")
2028                    .max_h_24()
2029                    .overflow_y_scroll()
2030                    .child(Label::new(ERROR_MESSAGE)),
2031            )
2032            .child(
2033                h_flex()
2034                    .justify_end()
2035                    .mt_1()
2036                    .gap_1()
2037                    .child(self.create_copy_button(ERROR_MESSAGE))
2038                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
2039                        |this, _, _, cx| {
2040                            this.thread.update(cx, |this, _cx| {
2041                                this.clear_last_error();
2042                            });
2043
2044                            cx.open_url(&zed_urls::account_url(cx));
2045                            cx.notify();
2046                        },
2047                    )))
2048                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2049                        |this, _, _, cx| {
2050                            this.thread.update(cx, |this, _cx| {
2051                                this.clear_last_error();
2052                            });
2053
2054                            cx.notify();
2055                        },
2056                    ))),
2057            )
2058            .into_any()
2059    }
2060
2061    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
2062        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
2063
2064        v_flex()
2065            .gap_0p5()
2066            .child(
2067                h_flex()
2068                    .gap_1p5()
2069                    .items_center()
2070                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2071                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
2072            )
2073            .child(
2074                div()
2075                    .id("error-message")
2076                    .max_h_24()
2077                    .overflow_y_scroll()
2078                    .child(Label::new(ERROR_MESSAGE)),
2079            )
2080            .child(
2081                h_flex()
2082                    .justify_end()
2083                    .mt_1()
2084                    .gap_1()
2085                    .child(self.create_copy_button(ERROR_MESSAGE))
2086                    .child(
2087                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
2088                            cx.listener(|this, _, _, cx| {
2089                                this.thread.update(cx, |this, _cx| {
2090                                    this.clear_last_error();
2091                                });
2092
2093                                cx.open_url(&zed_urls::account_url(cx));
2094                                cx.notify();
2095                            }),
2096                        ),
2097                    )
2098                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2099                        |this, _, _, cx| {
2100                            this.thread.update(cx, |this, _cx| {
2101                                this.clear_last_error();
2102                            });
2103
2104                            cx.notify();
2105                        },
2106                    ))),
2107            )
2108            .into_any()
2109    }
2110
2111    fn render_model_request_limit_reached_error(
2112        &self,
2113        plan: Plan,
2114        cx: &mut Context<Self>,
2115    ) -> AnyElement {
2116        let error_message = match plan {
2117            Plan::ZedPro => {
2118                "Model request limit reached. Upgrade to usage-based billing for more requests."
2119            }
2120            Plan::ZedProTrial => {
2121                "Model request limit reached. Upgrade to Zed Pro for more requests."
2122            }
2123            Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
2124        };
2125        let call_to_action = match plan {
2126            Plan::ZedPro => "Upgrade to usage-based billing",
2127            Plan::ZedProTrial => "Upgrade to Zed Pro",
2128            Plan::Free => "Upgrade to Zed Pro",
2129        };
2130
2131        v_flex()
2132            .gap_0p5()
2133            .child(
2134                h_flex()
2135                    .gap_1p5()
2136                    .items_center()
2137                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2138                    .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)),
2139            )
2140            .child(
2141                div()
2142                    .id("error-message")
2143                    .max_h_24()
2144                    .overflow_y_scroll()
2145                    .child(Label::new(error_message)),
2146            )
2147            .child(
2148                h_flex()
2149                    .justify_end()
2150                    .mt_1()
2151                    .gap_1()
2152                    .child(self.create_copy_button(error_message))
2153                    .child(
2154                        Button::new("subscribe", call_to_action).on_click(cx.listener(
2155                            |this, _, _, cx| {
2156                                this.thread.update(cx, |this, _cx| {
2157                                    this.clear_last_error();
2158                                });
2159
2160                                cx.open_url(&zed_urls::account_url(cx));
2161                                cx.notify();
2162                            },
2163                        )),
2164                    )
2165                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2166                        |this, _, _, cx| {
2167                            this.thread.update(cx, |this, _cx| {
2168                                this.clear_last_error();
2169                            });
2170
2171                            cx.notify();
2172                        },
2173                    ))),
2174            )
2175            .into_any()
2176    }
2177
2178    fn render_error_message(
2179        &self,
2180        header: SharedString,
2181        message: SharedString,
2182        cx: &mut Context<Self>,
2183    ) -> AnyElement {
2184        let message_with_header = format!("{}\n{}", header, message);
2185        v_flex()
2186            .gap_0p5()
2187            .child(
2188                h_flex()
2189                    .gap_1p5()
2190                    .items_center()
2191                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2192                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
2193            )
2194            .child(
2195                div()
2196                    .id("error-message")
2197                    .max_h_32()
2198                    .overflow_y_scroll()
2199                    .child(Label::new(message.clone())),
2200            )
2201            .child(
2202                h_flex()
2203                    .justify_end()
2204                    .mt_1()
2205                    .gap_1()
2206                    .child(self.create_copy_button(message_with_header))
2207                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2208                        |this, _, _, cx| {
2209                            this.thread.update(cx, |this, _cx| {
2210                                this.clear_last_error();
2211                            });
2212
2213                            cx.notify();
2214                        },
2215                    ))),
2216            )
2217            .into_any()
2218    }
2219
2220    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2221        let message = message.into();
2222        IconButton::new("copy", IconName::Copy)
2223            .on_click(move |_, _, cx| {
2224                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2225            })
2226            .tooltip(Tooltip::text("Copy Error Message"))
2227    }
2228
2229    fn key_context(&self) -> KeyContext {
2230        let mut key_context = KeyContext::new_with_defaults();
2231        key_context.add("AgentPanel");
2232        if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
2233            key_context.add("prompt_editor");
2234        }
2235        key_context
2236    }
2237}
2238
2239impl Render for AssistantPanel {
2240    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2241        v_flex()
2242            .key_context(self.key_context())
2243            .justify_between()
2244            .size_full()
2245            .on_action(cx.listener(Self::cancel))
2246            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
2247                this.new_thread(action, window, cx);
2248            }))
2249            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
2250                this.open_history(window, cx);
2251            }))
2252            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
2253                this.open_configuration(window, cx);
2254            }))
2255            .on_action(cx.listener(Self::open_active_thread_as_markdown))
2256            .on_action(cx.listener(Self::deploy_rules_library))
2257            .on_action(cx.listener(Self::open_agent_diff))
2258            .on_action(cx.listener(Self::go_back))
2259            .on_action(cx.listener(Self::toggle_navigation_menu))
2260            .on_action(cx.listener(Self::toggle_options_menu))
2261            .child(self.render_toolbar(window, cx))
2262            .map(|parent| match &self.active_view {
2263                ActiveView::Thread { .. } => parent
2264                    .child(self.render_active_thread_or_empty_state(window, cx))
2265                    .children(self.render_tool_use_limit_reached(cx))
2266                    .child(h_flex().child(self.message_editor.clone()))
2267                    .children(self.render_last_error(cx)),
2268                ActiveView::History => parent.child(self.history.clone()),
2269                ActiveView::PromptEditor { context_editor, .. } => {
2270                    parent.child(context_editor.clone())
2271                }
2272                ActiveView::Configuration => parent.children(self.configuration.clone()),
2273            })
2274    }
2275}
2276
2277struct PromptLibraryInlineAssist {
2278    workspace: WeakEntity<Workspace>,
2279}
2280
2281impl PromptLibraryInlineAssist {
2282    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
2283        Self { workspace }
2284    }
2285}
2286
2287impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
2288    fn assist(
2289        &self,
2290        prompt_editor: &Entity<Editor>,
2291        _initial_prompt: Option<String>,
2292        window: &mut Window,
2293        cx: &mut Context<RulesLibrary>,
2294    ) {
2295        InlineAssistant::update_global(cx, |assistant, cx| {
2296            let Some(project) = self
2297                .workspace
2298                .upgrade()
2299                .map(|workspace| workspace.read(cx).project().downgrade())
2300            else {
2301                return;
2302            };
2303            let prompt_store = None;
2304            let thread_store = None;
2305            assistant.assist(
2306                &prompt_editor,
2307                self.workspace.clone(),
2308                project,
2309                prompt_store,
2310                thread_store,
2311                window,
2312                cx,
2313            )
2314        })
2315    }
2316
2317    fn focus_assistant_panel(
2318        &self,
2319        workspace: &mut Workspace,
2320        window: &mut Window,
2321        cx: &mut Context<Workspace>,
2322    ) -> bool {
2323        workspace
2324            .focus_panel::<AssistantPanel>(window, cx)
2325            .is_some()
2326    }
2327}
2328
2329pub struct ConcreteAssistantPanelDelegate;
2330
2331impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
2332    fn active_context_editor(
2333        &self,
2334        workspace: &mut Workspace,
2335        _window: &mut Window,
2336        cx: &mut Context<Workspace>,
2337    ) -> Option<Entity<ContextEditor>> {
2338        let panel = workspace.panel::<AssistantPanel>(cx)?;
2339        panel.read(cx).active_context_editor()
2340    }
2341
2342    fn open_saved_context(
2343        &self,
2344        workspace: &mut Workspace,
2345        path: Arc<Path>,
2346        window: &mut Window,
2347        cx: &mut Context<Workspace>,
2348    ) -> Task<Result<()>> {
2349        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2350            return Task::ready(Err(anyhow!("Agent panel not found")));
2351        };
2352
2353        panel.update(cx, |panel, cx| {
2354            panel.open_saved_prompt_editor(path, window, cx)
2355        })
2356    }
2357
2358    fn open_remote_context(
2359        &self,
2360        _workspace: &mut Workspace,
2361        _context_id: assistant_context_editor::ContextId,
2362        _window: &mut Window,
2363        _cx: &mut Context<Workspace>,
2364    ) -> Task<Result<Entity<ContextEditor>>> {
2365        Task::ready(Err(anyhow!("opening remote context not implemented")))
2366    }
2367
2368    fn quote_selection(
2369        &self,
2370        workspace: &mut Workspace,
2371        selection_ranges: Vec<Range<Anchor>>,
2372        buffer: Entity<MultiBuffer>,
2373        window: &mut Window,
2374        cx: &mut Context<Workspace>,
2375    ) {
2376        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2377            return;
2378        };
2379
2380        if !panel.focus_handle(cx).contains_focused(window, cx) {
2381            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
2382        }
2383
2384        panel.update(cx, |_, cx| {
2385            // Wait to create a new context until the workspace is no longer
2386            // being updated.
2387            cx.defer_in(window, move |panel, window, cx| {
2388                if panel.has_active_thread() {
2389                    panel.message_editor.update(cx, |message_editor, cx| {
2390                        message_editor.context_store().update(cx, |store, cx| {
2391                            let buffer = buffer.read(cx);
2392                            let selection_ranges = selection_ranges
2393                                .into_iter()
2394                                .flat_map(|range| {
2395                                    let (start_buffer, start) =
2396                                        buffer.text_anchor_for_position(range.start, cx)?;
2397                                    let (end_buffer, end) =
2398                                        buffer.text_anchor_for_position(range.end, cx)?;
2399                                    if start_buffer != end_buffer {
2400                                        return None;
2401                                    }
2402                                    Some((start_buffer, start..end))
2403                                })
2404                                .collect::<Vec<_>>();
2405
2406                            for (buffer, range) in selection_ranges {
2407                                store.add_selection(buffer, range, cx);
2408                            }
2409                        })
2410                    })
2411                } else if let Some(context_editor) = panel.active_context_editor() {
2412                    let snapshot = buffer.read(cx).snapshot(cx);
2413                    let selection_ranges = selection_ranges
2414                        .into_iter()
2415                        .map(|range| range.to_point(&snapshot))
2416                        .collect::<Vec<_>>();
2417
2418                    context_editor.update(cx, |context_editor, cx| {
2419                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
2420                    });
2421                }
2422            });
2423        });
2424    }
2425}