assistant_panel.rs

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