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