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