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