assistant_panel.rs

   1use std::ops::Range;
   2use std::path::Path;
   3use std::sync::Arc;
   4use std::time::Duration;
   5
   6use db::kvp::KEY_VALUE_STORE;
   7use serde::{Deserialize, Serialize};
   8
   9use anyhow::{Result, anyhow};
  10use assistant_context_editor::{
  11    AssistantContext, AssistantPanelDelegate, ConfigurationError, ContextEditor, ContextEvent,
  12    SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
  13    render_remaining_tokens,
  14};
  15use assistant_settings::{AssistantDockPosition, AssistantSettings};
  16use assistant_slash_command::SlashCommandWorkingSet;
  17use assistant_tool::ToolWorkingSet;
  18
  19use client::{UserStore, zed_urls};
  20use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  21use fs::Fs;
  22use gpui::{
  23    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  24    Corner, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext,
  25    Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
  26};
  27use language::LanguageRegistry;
  28use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
  29use language_model_selector::ToggleModelSelector;
  30use project::Project;
  31use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  32use proto::Plan;
  33use rules_library::{RulesLibrary, open_rules_library};
  34use 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 _;
  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_manager = self.thread_store.read(cx).context_server_manager();
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_manager, 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 thread = active_thread.thread().read(cx);
1392        let thread_id = thread.id().clone();
1393        let is_empty = active_thread.is_empty();
1394        let last_usage = active_thread.thread().read(cx).last_usage();
1395        let account_url = zed_urls::account_url(cx);
1396
1397        let show_token_count = match &self.active_view {
1398            ActiveView::Thread { .. } => !is_empty,
1399            ActiveView::PromptEditor { .. } => true,
1400            _ => false,
1401        };
1402
1403        let focus_handle = self.focus_handle(cx);
1404
1405        let go_back_button = div().child(
1406            IconButton::new("go-back", IconName::ArrowLeft)
1407                .icon_size(IconSize::Small)
1408                .on_click(cx.listener(|this, _, window, cx| {
1409                    this.go_back(&workspace::GoBack, window, cx);
1410                }))
1411                .tooltip({
1412                    let focus_handle = focus_handle.clone();
1413                    move |window, cx| {
1414                        Tooltip::for_action_in(
1415                            "Go Back",
1416                            &workspace::GoBack,
1417                            &focus_handle,
1418                            window,
1419                            cx,
1420                        )
1421                    }
1422                }),
1423        );
1424
1425        let recent_entries_menu = div().child(
1426            PopoverMenu::new("agent-nav-menu")
1427                .trigger_with_tooltip(
1428                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
1429                        .icon_size(IconSize::Small)
1430                        .style(ui::ButtonStyle::Subtle),
1431                    {
1432                        let focus_handle = focus_handle.clone();
1433                        move |window, cx| {
1434                            Tooltip::for_action_in(
1435                                "Toggle Panel Menu",
1436                                &ToggleNavigationMenu,
1437                                &focus_handle,
1438                                window,
1439                                cx,
1440                            )
1441                        }
1442                    },
1443                )
1444                .anchor(Corner::TopLeft)
1445                .with_handle(self.assistant_navigation_menu_handle.clone())
1446                .menu({
1447                    let menu = self.assistant_navigation_menu.clone();
1448                    move |window, cx| {
1449                        if let Some(menu) = menu.as_ref() {
1450                            menu.update(cx, |_, cx| {
1451                                cx.defer_in(window, |menu, window, cx| {
1452                                    menu.rebuild(window, cx);
1453                                });
1454                            })
1455                        }
1456                        menu.clone()
1457                    }
1458                }),
1459        );
1460
1461        let agent_extra_menu = PopoverMenu::new("agent-options-menu")
1462            .trigger_with_tooltip(
1463                IconButton::new("agent-options-menu", IconName::Ellipsis)
1464                    .icon_size(IconSize::Small),
1465                {
1466                    let focus_handle = focus_handle.clone();
1467                    move |window, cx| {
1468                        Tooltip::for_action_in(
1469                            "Toggle Agent Menu",
1470                            &ToggleOptionsMenu,
1471                            &focus_handle,
1472                            window,
1473                            cx,
1474                        )
1475                    }
1476                },
1477            )
1478            .anchor(Corner::TopRight)
1479            .with_handle(self.assistant_dropdown_menu_handle.clone())
1480            .menu(move |window, cx| {
1481                Some(ContextMenu::build(window, cx, |mut menu, _window, _cx| {
1482                    menu = menu
1483                        .action("New Thread", NewThread::default().boxed_clone())
1484                        .action("New Text Thread", NewTextThread.boxed_clone())
1485                        .when(!is_empty, |menu| {
1486                            menu.action(
1487                                "New From Summary",
1488                                Box::new(NewThread {
1489                                    from_thread_id: Some(thread_id.clone()),
1490                                }),
1491                            )
1492                        })
1493                        .separator();
1494
1495                    menu = menu
1496                        .header("MCP Servers")
1497                        .action(
1498                            "View Server Extensions",
1499                            Box::new(zed_actions::Extensions {
1500                                category_filter: Some(
1501                                    zed_actions::ExtensionCategoryFilter::ContextServers,
1502                                ),
1503                            }),
1504                        )
1505                        .action("Add Custom Server…", Box::new(AddContextServer))
1506                        .separator();
1507
1508                    if let Some(usage) = last_usage {
1509                        menu = menu
1510                            .header_with_link("Prompt Usage", "Manage", account_url.clone())
1511                            .custom_entry(
1512                                move |_window, cx| {
1513                                    let used_percentage = match usage.limit {
1514                                        UsageLimit::Limited(limit) => {
1515                                            Some((usage.amount as f32 / limit as f32) * 100.)
1516                                        }
1517                                        UsageLimit::Unlimited => None,
1518                                    };
1519
1520                                    h_flex()
1521                                        .flex_1()
1522                                        .gap_1p5()
1523                                        .children(used_percentage.map(|percent| {
1524                                            ProgressBar::new("usage", percent, 100., cx)
1525                                        }))
1526                                        .child(
1527                                            Label::new(match usage.limit {
1528                                                UsageLimit::Limited(limit) => {
1529                                                    format!("{} / {limit}", usage.amount)
1530                                                }
1531                                                UsageLimit::Unlimited => {
1532                                                    format!("{} / ∞", usage.amount)
1533                                                }
1534                                            })
1535                                            .size(LabelSize::Small)
1536                                            .color(Color::Muted),
1537                                        )
1538                                        .into_any_element()
1539                                },
1540                                move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
1541                            )
1542                            .separator()
1543                    }
1544
1545                    menu = menu
1546                        .action("Rules…", Box::new(OpenRulesLibrary::default()))
1547                        .action("Settings", Box::new(OpenConfiguration));
1548                    menu
1549                }))
1550            });
1551
1552        h_flex()
1553            .id("assistant-toolbar")
1554            .h(Tab::container_height(cx))
1555            .max_w_full()
1556            .flex_none()
1557            .justify_between()
1558            .gap_2()
1559            .bg(cx.theme().colors().tab_bar_background)
1560            .border_b_1()
1561            .border_color(cx.theme().colors().border)
1562            .child(
1563                h_flex()
1564                    .size_full()
1565                    .pl_1()
1566                    .gap_1()
1567                    .child(match &self.active_view {
1568                        ActiveView::History | ActiveView::Configuration => go_back_button,
1569                        _ => recent_entries_menu,
1570                    })
1571                    .child(self.render_title_view(window, cx)),
1572            )
1573            .child(
1574                h_flex()
1575                    .h_full()
1576                    .gap_2()
1577                    .when(show_token_count, |parent| {
1578                        parent.children(self.render_token_count(&thread, cx))
1579                    })
1580                    .child(
1581                        h_flex()
1582                            .h_full()
1583                            .gap(DynamicSpacing::Base02.rems(cx))
1584                            .px(DynamicSpacing::Base08.rems(cx))
1585                            .border_l_1()
1586                            .border_color(cx.theme().colors().border)
1587                            .child(
1588                                IconButton::new("new", IconName::Plus)
1589                                    .icon_size(IconSize::Small)
1590                                    .style(ButtonStyle::Subtle)
1591                                    .tooltip(move |window, cx| {
1592                                        Tooltip::for_action_in(
1593                                            "New Thread",
1594                                            &NewThread::default(),
1595                                            &focus_handle,
1596                                            window,
1597                                            cx,
1598                                        )
1599                                    })
1600                                    .on_click(move |_event, window, cx| {
1601                                        window.dispatch_action(
1602                                            NewThread::default().boxed_clone(),
1603                                            cx,
1604                                        );
1605                                    }),
1606                            )
1607                            .child(agent_extra_menu),
1608                    ),
1609            )
1610    }
1611
1612    fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
1613        let is_generating = thread.is_generating();
1614        let message_editor = self.message_editor.read(cx);
1615
1616        let conversation_token_usage = thread.total_token_usage()?;
1617
1618        let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
1619            self.thread.read(cx).editing_message_id()
1620        {
1621            let combined = thread
1622                .token_usage_up_to_message(editing_message_id)
1623                .add(unsent_tokens);
1624
1625            (combined, unsent_tokens > 0)
1626        } else {
1627            let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
1628            let combined = conversation_token_usage.add(unsent_tokens);
1629
1630            (combined, unsent_tokens > 0)
1631        };
1632
1633        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
1634
1635        match &self.active_view {
1636            ActiveView::Thread { .. } => {
1637                if total_token_usage.total == 0 {
1638                    return None;
1639                }
1640
1641                let token_color = match total_token_usage.ratio() {
1642                    TokenUsageRatio::Normal if is_estimating => Color::Default,
1643                    TokenUsageRatio::Normal => Color::Muted,
1644                    TokenUsageRatio::Warning => Color::Warning,
1645                    TokenUsageRatio::Exceeded => Color::Error,
1646                };
1647
1648                let token_count = h_flex()
1649                    .id("token-count")
1650                    .flex_shrink_0()
1651                    .gap_0p5()
1652                    .when(!is_generating && is_estimating, |parent| {
1653                        parent
1654                            .child(
1655                                h_flex()
1656                                    .mr_1()
1657                                    .size_2p5()
1658                                    .justify_center()
1659                                    .rounded_full()
1660                                    .bg(cx.theme().colors().text.opacity(0.1))
1661                                    .child(
1662                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
1663                                    ),
1664                            )
1665                            .tooltip(move |window, cx| {
1666                                Tooltip::with_meta(
1667                                    "Estimated New Token Count",
1668                                    None,
1669                                    format!(
1670                                        "Current Conversation Tokens: {}",
1671                                        humanize_token_count(conversation_token_usage.total)
1672                                    ),
1673                                    window,
1674                                    cx,
1675                                )
1676                            })
1677                    })
1678                    .child(
1679                        Label::new(humanize_token_count(total_token_usage.total))
1680                            .size(LabelSize::Small)
1681                            .color(token_color)
1682                            .map(|label| {
1683                                if is_generating || is_waiting_to_update_token_count {
1684                                    label
1685                                        .with_animation(
1686                                            "used-tokens-label",
1687                                            Animation::new(Duration::from_secs(2))
1688                                                .repeat()
1689                                                .with_easing(pulsating_between(0.6, 1.)),
1690                                            |label, delta| label.alpha(delta),
1691                                        )
1692                                        .into_any()
1693                                } else {
1694                                    label.into_any_element()
1695                                }
1696                            }),
1697                    )
1698                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
1699                    .child(
1700                        Label::new(humanize_token_count(total_token_usage.max))
1701                            .size(LabelSize::Small)
1702                            .color(Color::Muted),
1703                    )
1704                    .into_any();
1705
1706                Some(token_count)
1707            }
1708            ActiveView::PromptEditor { context_editor, .. } => {
1709                let element = render_remaining_tokens(context_editor, cx)?;
1710
1711                Some(element.into_any_element())
1712            }
1713            _ => None,
1714        }
1715    }
1716
1717    fn render_active_thread_or_empty_state(
1718        &self,
1719        window: &mut Window,
1720        cx: &mut Context<Self>,
1721    ) -> AnyElement {
1722        if self.thread.read(cx).is_empty() {
1723            return self
1724                .render_thread_empty_state(window, cx)
1725                .into_any_element();
1726        }
1727
1728        self.thread.clone().into_any_element()
1729    }
1730
1731    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
1732        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
1733            return Some(ConfigurationError::NoProvider);
1734        };
1735
1736        if !model.provider.is_authenticated(cx) {
1737            return Some(ConfigurationError::ProviderNotAuthenticated);
1738        }
1739
1740        if model.provider.must_accept_terms(cx) {
1741            return Some(ConfigurationError::ProviderPendingTermsAcceptance(
1742                model.provider,
1743            ));
1744        }
1745
1746        None
1747    }
1748
1749    fn render_thread_empty_state(
1750        &self,
1751        window: &mut Window,
1752        cx: &mut Context<Self>,
1753    ) -> impl IntoElement {
1754        let recent_history = self
1755            .history_store
1756            .update(cx, |this, cx| this.recent_entries(6, cx));
1757
1758        let configuration_error = self.configuration_error(cx);
1759        let no_error = configuration_error.is_none();
1760        let focus_handle = self.focus_handle(cx);
1761
1762        v_flex()
1763            .size_full()
1764            .when(recent_history.is_empty(), |this| {
1765                let configuration_error_ref = &configuration_error;
1766                this.child(
1767                    v_flex()
1768                        .size_full()
1769                        .max_w_80()
1770                        .mx_auto()
1771                        .justify_center()
1772                        .items_center()
1773                        .gap_1()
1774                        .child(
1775                            h_flex().child(
1776                                Headline::new("Welcome to the Agent Panel")
1777                            ),
1778                        )
1779                        .when(no_error, |parent| {
1780                            parent
1781                                .child(
1782                                    h_flex().child(
1783                                        Label::new("Ask and build anything.")
1784                                            .color(Color::Muted)
1785                                            .mb_2p5(),
1786                                    ),
1787                                )
1788                                .child(
1789                                    Button::new("new-thread", "Start New Thread")
1790                                        .icon(IconName::Plus)
1791                                        .icon_position(IconPosition::Start)
1792                                        .icon_size(IconSize::Small)
1793                                        .icon_color(Color::Muted)
1794                                        .full_width()
1795                                        .key_binding(KeyBinding::for_action_in(
1796                                            &NewThread::default(),
1797                                            &focus_handle,
1798                                            window,
1799                                            cx,
1800                                        ))
1801                                        .on_click(|_event, window, cx| {
1802                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
1803                                        }),
1804                                )
1805                                .child(
1806                                    Button::new("context", "Add Context")
1807                                        .icon(IconName::FileCode)
1808                                        .icon_position(IconPosition::Start)
1809                                        .icon_size(IconSize::Small)
1810                                        .icon_color(Color::Muted)
1811                                        .full_width()
1812                                        .key_binding(KeyBinding::for_action_in(
1813                                            &ToggleContextPicker,
1814                                            &focus_handle,
1815                                            window,
1816                                            cx,
1817                                        ))
1818                                        .on_click(|_event, window, cx| {
1819                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
1820                                        }),
1821                                )
1822                                .child(
1823                                    Button::new("mode", "Switch Model")
1824                                        .icon(IconName::DatabaseZap)
1825                                        .icon_position(IconPosition::Start)
1826                                        .icon_size(IconSize::Small)
1827                                        .icon_color(Color::Muted)
1828                                        .full_width()
1829                                        .key_binding(KeyBinding::for_action_in(
1830                                            &ToggleModelSelector,
1831                                            &focus_handle,
1832                                            window,
1833                                            cx,
1834                                        ))
1835                                        .on_click(|_event, window, cx| {
1836                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
1837                                        }),
1838                                )
1839                                .child(
1840                                    Button::new("settings", "View Settings")
1841                                        .icon(IconName::Settings)
1842                                        .icon_position(IconPosition::Start)
1843                                        .icon_size(IconSize::Small)
1844                                        .icon_color(Color::Muted)
1845                                        .full_width()
1846                                        .key_binding(KeyBinding::for_action_in(
1847                                            &OpenConfiguration,
1848                                            &focus_handle,
1849                                            window,
1850                                            cx,
1851                                        ))
1852                                        .on_click(|_event, window, cx| {
1853                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1854                                        }),
1855                                )
1856                        })
1857                        .map(|parent| {
1858                            match configuration_error_ref {
1859                                Some(ConfigurationError::ProviderNotAuthenticated)
1860                                | Some(ConfigurationError::NoProvider) => {
1861                                    parent
1862                                        .child(
1863                                            h_flex().child(
1864                                                Label::new("To start using the agent, configure at least one LLM provider.")
1865                                                    .color(Color::Muted)
1866                                                    .mb_2p5()
1867                                            )
1868                                        )
1869                                        .child(
1870                                            Button::new("settings", "Configure a Provider")
1871                                                .icon(IconName::Settings)
1872                                                .icon_position(IconPosition::Start)
1873                                                .icon_size(IconSize::Small)
1874                                                .icon_color(Color::Muted)
1875                                                .full_width()
1876                                                .key_binding(KeyBinding::for_action_in(
1877                                                    &OpenConfiguration,
1878                                                    &focus_handle,
1879                                                    window,
1880                                                    cx,
1881                                                ))
1882                                                .on_click(|_event, window, cx| {
1883                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1884                                                }),
1885                                        )
1886                                }
1887                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1888                                    parent.children(
1889                                        provider.render_accept_terms(
1890                                            LanguageModelProviderTosView::ThreadFreshStart,
1891                                            cx,
1892                                        ),
1893                                    )
1894                                }
1895                                None => parent,
1896                            }
1897                        })
1898                )
1899            })
1900            .when(!recent_history.is_empty(), |parent| {
1901                let focus_handle = focus_handle.clone();
1902                let configuration_error_ref = &configuration_error;
1903
1904                parent
1905                    .overflow_hidden()
1906                    .p_1p5()
1907                    .justify_end()
1908                    .gap_1()
1909                    .child(
1910                        h_flex()
1911                            .pl_1p5()
1912                            .pb_1()
1913                            .w_full()
1914                            .justify_between()
1915                            .border_b_1()
1916                            .border_color(cx.theme().colors().border_variant)
1917                            .child(
1918                                Label::new("Past Interactions")
1919                                    .size(LabelSize::Small)
1920                                    .color(Color::Muted),
1921                            )
1922                            .child(
1923                                Button::new("view-history", "View All")
1924                                    .style(ButtonStyle::Subtle)
1925                                    .label_size(LabelSize::Small)
1926                                    .key_binding(
1927                                        KeyBinding::for_action_in(
1928                                            &OpenHistory,
1929                                            &self.focus_handle(cx),
1930                                            window,
1931                                            cx,
1932                                        ).map(|kb| kb.size(rems_from_px(12.))),
1933                                    )
1934                                    .on_click(move |_event, window, cx| {
1935                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1936                                    }),
1937                            ),
1938                    )
1939                    .child(
1940                        v_flex()
1941                            .gap_1()
1942                            .children(
1943                                recent_history.into_iter().map(|entry| {
1944                                    // TODO: Add keyboard navigation.
1945                                    match entry {
1946                                        HistoryEntry::Thread(thread) => {
1947                                            PastThread::new(thread, cx.entity().downgrade(), false, vec![])
1948                                                .into_any_element()
1949                                        }
1950                                        HistoryEntry::Context(context) => {
1951                                            PastContext::new(context, cx.entity().downgrade(), false, vec![])
1952                                                .into_any_element()
1953                                        }
1954                                    }
1955                                }),
1956                            )
1957                    )
1958                    .map(|parent| {
1959                        match configuration_error_ref {
1960                            Some(ConfigurationError::ProviderNotAuthenticated)
1961                            | Some(ConfigurationError::NoProvider) => {
1962                                parent
1963                                    .child(
1964                                        Banner::new()
1965                                            .severity(ui::Severity::Warning)
1966                                            .child(
1967                                                Label::new(
1968                                                    "Configure at least one LLM provider to start using the panel.",
1969                                                )
1970                                                .size(LabelSize::Small),
1971                                            )
1972                                            .action_slot(
1973                                                Button::new("settings", "Configure Provider")
1974                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
1975                                                    .label_size(LabelSize::Small)
1976                                                    .key_binding(
1977                                                        KeyBinding::for_action_in(
1978                                                            &OpenConfiguration,
1979                                                            &focus_handle,
1980                                                            window,
1981                                                            cx,
1982                                                        )
1983                                                        .map(|kb| kb.size(rems_from_px(12.))),
1984                                                    )
1985                                                    .on_click(|_event, window, cx| {
1986                                                        window.dispatch_action(
1987                                                            OpenConfiguration.boxed_clone(),
1988                                                            cx,
1989                                                        )
1990                                                    }),
1991                                            ),
1992                                    )
1993                            }
1994                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1995                                parent
1996                                    .child(
1997                                        Banner::new()
1998                                            .severity(ui::Severity::Warning)
1999                                            .child(
2000                                                h_flex()
2001                                                    .w_full()
2002                                                    .children(
2003                                                        provider.render_accept_terms(
2004                                                            LanguageModelProviderTosView::ThreadtEmptyState,
2005                                                            cx,
2006                                                        ),
2007                                                    ),
2008                                            ),
2009                                    )
2010                            }
2011                            None => parent,
2012                        }
2013                    })
2014            })
2015    }
2016
2017    fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2018        let tool_use_limit_reached = self
2019            .thread
2020            .read(cx)
2021            .thread()
2022            .read(cx)
2023            .tool_use_limit_reached();
2024        if !tool_use_limit_reached {
2025            return None;
2026        }
2027
2028        let model = self
2029            .thread
2030            .read(cx)
2031            .thread()
2032            .read(cx)
2033            .configured_model()?
2034            .model;
2035
2036        let max_mode_upsell = if model.supports_max_mode() {
2037            " Enable max mode for unlimited tool use."
2038        } else {
2039            ""
2040        };
2041
2042        Some(
2043            Banner::new()
2044                .severity(ui::Severity::Info)
2045                .child(h_flex().child(Label::new(format!(
2046                    "Consecutive tool use limit reached.{max_mode_upsell}"
2047                ))))
2048                .into_any_element(),
2049        )
2050    }
2051
2052    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2053        let last_error = self.thread.read(cx).last_error()?;
2054
2055        Some(
2056            div()
2057                .absolute()
2058                .right_3()
2059                .bottom_12()
2060                .max_w_96()
2061                .py_2()
2062                .px_3()
2063                .elevation_2(cx)
2064                .occlude()
2065                .child(match last_error {
2066                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
2067                    ThreadError::MaxMonthlySpendReached => {
2068                        self.render_max_monthly_spend_reached_error(cx)
2069                    }
2070                    ThreadError::ModelRequestLimitReached { plan } => {
2071                        self.render_model_request_limit_reached_error(plan, cx)
2072                    }
2073                    ThreadError::Message { header, message } => {
2074                        self.render_error_message(header, message, cx)
2075                    }
2076                })
2077                .into_any(),
2078        )
2079    }
2080
2081    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
2082        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.";
2083
2084        v_flex()
2085            .gap_0p5()
2086            .child(
2087                h_flex()
2088                    .gap_1p5()
2089                    .items_center()
2090                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2091                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
2092            )
2093            .child(
2094                div()
2095                    .id("error-message")
2096                    .max_h_24()
2097                    .overflow_y_scroll()
2098                    .child(Label::new(ERROR_MESSAGE)),
2099            )
2100            .child(
2101                h_flex()
2102                    .justify_end()
2103                    .mt_1()
2104                    .gap_1()
2105                    .child(self.create_copy_button(ERROR_MESSAGE))
2106                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
2107                        |this, _, _, cx| {
2108                            this.thread.update(cx, |this, _cx| {
2109                                this.clear_last_error();
2110                            });
2111
2112                            cx.open_url(&zed_urls::account_url(cx));
2113                            cx.notify();
2114                        },
2115                    )))
2116                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2117                        |this, _, _, cx| {
2118                            this.thread.update(cx, |this, _cx| {
2119                                this.clear_last_error();
2120                            });
2121
2122                            cx.notify();
2123                        },
2124                    ))),
2125            )
2126            .into_any()
2127    }
2128
2129    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
2130        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
2131
2132        v_flex()
2133            .gap_0p5()
2134            .child(
2135                h_flex()
2136                    .gap_1p5()
2137                    .items_center()
2138                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2139                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
2140            )
2141            .child(
2142                div()
2143                    .id("error-message")
2144                    .max_h_24()
2145                    .overflow_y_scroll()
2146                    .child(Label::new(ERROR_MESSAGE)),
2147            )
2148            .child(
2149                h_flex()
2150                    .justify_end()
2151                    .mt_1()
2152                    .gap_1()
2153                    .child(self.create_copy_button(ERROR_MESSAGE))
2154                    .child(
2155                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
2156                            cx.listener(|this, _, _, cx| {
2157                                this.thread.update(cx, |this, _cx| {
2158                                    this.clear_last_error();
2159                                });
2160
2161                                cx.open_url(&zed_urls::account_url(cx));
2162                                cx.notify();
2163                            }),
2164                        ),
2165                    )
2166                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2167                        |this, _, _, cx| {
2168                            this.thread.update(cx, |this, _cx| {
2169                                this.clear_last_error();
2170                            });
2171
2172                            cx.notify();
2173                        },
2174                    ))),
2175            )
2176            .into_any()
2177    }
2178
2179    fn render_model_request_limit_reached_error(
2180        &self,
2181        plan: Plan,
2182        cx: &mut Context<Self>,
2183    ) -> AnyElement {
2184        let error_message = match plan {
2185            Plan::ZedPro => {
2186                "Model request limit reached. Upgrade to usage-based billing for more requests."
2187            }
2188            Plan::ZedProTrial => {
2189                "Model request limit reached. Upgrade to Zed Pro for more requests."
2190            }
2191            Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
2192        };
2193        let call_to_action = match plan {
2194            Plan::ZedPro => "Upgrade to usage-based billing",
2195            Plan::ZedProTrial => "Upgrade to Zed Pro",
2196            Plan::Free => "Upgrade to Zed Pro",
2197        };
2198
2199        v_flex()
2200            .gap_0p5()
2201            .child(
2202                h_flex()
2203                    .gap_1p5()
2204                    .items_center()
2205                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2206                    .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)),
2207            )
2208            .child(
2209                div()
2210                    .id("error-message")
2211                    .max_h_24()
2212                    .overflow_y_scroll()
2213                    .child(Label::new(error_message)),
2214            )
2215            .child(
2216                h_flex()
2217                    .justify_end()
2218                    .mt_1()
2219                    .gap_1()
2220                    .child(self.create_copy_button(error_message))
2221                    .child(
2222                        Button::new("subscribe", call_to_action).on_click(cx.listener(
2223                            |this, _, _, cx| {
2224                                this.thread.update(cx, |this, _cx| {
2225                                    this.clear_last_error();
2226                                });
2227
2228                                cx.open_url(&zed_urls::account_url(cx));
2229                                cx.notify();
2230                            },
2231                        )),
2232                    )
2233                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2234                        |this, _, _, cx| {
2235                            this.thread.update(cx, |this, _cx| {
2236                                this.clear_last_error();
2237                            });
2238
2239                            cx.notify();
2240                        },
2241                    ))),
2242            )
2243            .into_any()
2244    }
2245
2246    fn render_error_message(
2247        &self,
2248        header: SharedString,
2249        message: SharedString,
2250        cx: &mut Context<Self>,
2251    ) -> AnyElement {
2252        let message_with_header = format!("{}\n{}", header, message);
2253        v_flex()
2254            .gap_0p5()
2255            .child(
2256                h_flex()
2257                    .gap_1p5()
2258                    .items_center()
2259                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2260                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
2261            )
2262            .child(
2263                div()
2264                    .id("error-message")
2265                    .max_h_32()
2266                    .overflow_y_scroll()
2267                    .child(Label::new(message.clone())),
2268            )
2269            .child(
2270                h_flex()
2271                    .justify_end()
2272                    .mt_1()
2273                    .gap_1()
2274                    .child(self.create_copy_button(message_with_header))
2275                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2276                        |this, _, _, cx| {
2277                            this.thread.update(cx, |this, _cx| {
2278                                this.clear_last_error();
2279                            });
2280
2281                            cx.notify();
2282                        },
2283                    ))),
2284            )
2285            .into_any()
2286    }
2287
2288    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2289        let message = message.into();
2290        IconButton::new("copy", IconName::Copy)
2291            .on_click(move |_, _, cx| {
2292                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2293            })
2294            .tooltip(Tooltip::text("Copy Error Message"))
2295    }
2296
2297    fn key_context(&self) -> KeyContext {
2298        let mut key_context = KeyContext::new_with_defaults();
2299        key_context.add("AgentPanel");
2300        if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
2301            key_context.add("prompt_editor");
2302        }
2303        key_context
2304    }
2305}
2306
2307impl Render for AssistantPanel {
2308    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2309        v_flex()
2310            .key_context(self.key_context())
2311            .justify_between()
2312            .size_full()
2313            .on_action(cx.listener(Self::cancel))
2314            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
2315                this.new_thread(action, window, cx);
2316            }))
2317            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
2318                this.open_history(window, cx);
2319            }))
2320            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
2321                this.open_configuration(window, cx);
2322            }))
2323            .on_action(cx.listener(Self::open_active_thread_as_markdown))
2324            .on_action(cx.listener(Self::deploy_rules_library))
2325            .on_action(cx.listener(Self::open_agent_diff))
2326            .on_action(cx.listener(Self::go_back))
2327            .on_action(cx.listener(Self::toggle_navigation_menu))
2328            .on_action(cx.listener(Self::toggle_options_menu))
2329            .child(self.render_toolbar(window, cx))
2330            .map(|parent| match &self.active_view {
2331                ActiveView::Thread { .. } => parent
2332                    .child(self.render_active_thread_or_empty_state(window, cx))
2333                    .children(self.render_tool_use_limit_reached(cx))
2334                    .child(h_flex().child(self.message_editor.clone()))
2335                    .children(self.render_last_error(cx)),
2336                ActiveView::History => parent.child(self.history.clone()),
2337                ActiveView::PromptEditor {
2338                    context_editor,
2339                    buffer_search_bar,
2340                    ..
2341                } => {
2342                    let mut registrar = DivRegistrar::new(
2343                        |this, _, _cx| match &this.active_view {
2344                            ActiveView::PromptEditor {
2345                                buffer_search_bar, ..
2346                            } => Some(buffer_search_bar.clone()),
2347                            _ => None,
2348                        },
2349                        cx,
2350                    );
2351                    BufferSearchBar::register(&mut registrar);
2352                    parent.child(
2353                        registrar
2354                            .into_div()
2355                            .size_full()
2356                            .map(|parent| {
2357                                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
2358                                    if buffer_search_bar.is_dismissed() {
2359                                        return parent;
2360                                    }
2361                                    parent.child(
2362                                        div()
2363                                            .p(DynamicSpacing::Base08.rems(cx))
2364                                            .border_b_1()
2365                                            .border_color(cx.theme().colors().border_variant)
2366                                            .bg(cx.theme().colors().editor_background)
2367                                            .child(buffer_search_bar.render(window, cx)),
2368                                    )
2369                                })
2370                            })
2371                            .child(context_editor.clone()),
2372                    )
2373                }
2374                ActiveView::Configuration => parent.children(self.configuration.clone()),
2375            })
2376    }
2377}
2378
2379struct PromptLibraryInlineAssist {
2380    workspace: WeakEntity<Workspace>,
2381}
2382
2383impl PromptLibraryInlineAssist {
2384    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
2385        Self { workspace }
2386    }
2387}
2388
2389impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
2390    fn assist(
2391        &self,
2392        prompt_editor: &Entity<Editor>,
2393        _initial_prompt: Option<String>,
2394        window: &mut Window,
2395        cx: &mut Context<RulesLibrary>,
2396    ) {
2397        InlineAssistant::update_global(cx, |assistant, cx| {
2398            let Some(project) = self
2399                .workspace
2400                .upgrade()
2401                .map(|workspace| workspace.read(cx).project().downgrade())
2402            else {
2403                return;
2404            };
2405            let prompt_store = None;
2406            let thread_store = None;
2407            assistant.assist(
2408                &prompt_editor,
2409                self.workspace.clone(),
2410                project,
2411                prompt_store,
2412                thread_store,
2413                window,
2414                cx,
2415            )
2416        })
2417    }
2418
2419    fn focus_assistant_panel(
2420        &self,
2421        workspace: &mut Workspace,
2422        window: &mut Window,
2423        cx: &mut Context<Workspace>,
2424    ) -> bool {
2425        workspace
2426            .focus_panel::<AssistantPanel>(window, cx)
2427            .is_some()
2428    }
2429}
2430
2431pub struct ConcreteAssistantPanelDelegate;
2432
2433impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
2434    fn active_context_editor(
2435        &self,
2436        workspace: &mut Workspace,
2437        _window: &mut Window,
2438        cx: &mut Context<Workspace>,
2439    ) -> Option<Entity<ContextEditor>> {
2440        let panel = workspace.panel::<AssistantPanel>(cx)?;
2441        panel.read(cx).active_context_editor()
2442    }
2443
2444    fn open_saved_context(
2445        &self,
2446        workspace: &mut Workspace,
2447        path: Arc<Path>,
2448        window: &mut Window,
2449        cx: &mut Context<Workspace>,
2450    ) -> Task<Result<()>> {
2451        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2452            return Task::ready(Err(anyhow!("Agent panel not found")));
2453        };
2454
2455        panel.update(cx, |panel, cx| {
2456            panel.open_saved_prompt_editor(path, window, cx)
2457        })
2458    }
2459
2460    fn open_remote_context(
2461        &self,
2462        _workspace: &mut Workspace,
2463        _context_id: assistant_context_editor::ContextId,
2464        _window: &mut Window,
2465        _cx: &mut Context<Workspace>,
2466    ) -> Task<Result<Entity<ContextEditor>>> {
2467        Task::ready(Err(anyhow!("opening remote context not implemented")))
2468    }
2469
2470    fn quote_selection(
2471        &self,
2472        workspace: &mut Workspace,
2473        selection_ranges: Vec<Range<Anchor>>,
2474        buffer: Entity<MultiBuffer>,
2475        window: &mut Window,
2476        cx: &mut Context<Workspace>,
2477    ) {
2478        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2479            return;
2480        };
2481
2482        if !panel.focus_handle(cx).contains_focused(window, cx) {
2483            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
2484        }
2485
2486        panel.update(cx, |_, cx| {
2487            // Wait to create a new context until the workspace is no longer
2488            // being updated.
2489            cx.defer_in(window, move |panel, window, cx| {
2490                if panel.has_active_thread() {
2491                    panel.message_editor.update(cx, |message_editor, cx| {
2492                        message_editor.context_store().update(cx, |store, cx| {
2493                            let buffer = buffer.read(cx);
2494                            let selection_ranges = selection_ranges
2495                                .into_iter()
2496                                .flat_map(|range| {
2497                                    let (start_buffer, start) =
2498                                        buffer.text_anchor_for_position(range.start, cx)?;
2499                                    let (end_buffer, end) =
2500                                        buffer.text_anchor_for_position(range.end, cx)?;
2501                                    if start_buffer != end_buffer {
2502                                        return None;
2503                                    }
2504                                    Some((start_buffer, start..end))
2505                                })
2506                                .collect::<Vec<_>>();
2507
2508                            for (buffer, range) in selection_ranges {
2509                                store.add_selection(buffer, range, cx);
2510                            }
2511                        })
2512                    })
2513                } else if let Some(context_editor) = panel.active_context_editor() {
2514                    let snapshot = buffer.read(cx).snapshot(cx);
2515                    let selection_ranges = selection_ranges
2516                        .into_iter()
2517                        .map(|range| range.to_point(&snapshot))
2518                        .collect::<Vec<_>>();
2519
2520                    context_editor.update(cx, |context_editor, cx| {
2521                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
2522                    });
2523                }
2524            });
2525        });
2526    }
2527}