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