assistant_panel.rs

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