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