assistant_panel.rs

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