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