assistant_panel.rs

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