assistant_panel.rs

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