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