agent_panel.rs

   1use std::ops::Range;
   2use std::path::Path;
   3use std::sync::Arc;
   4use std::time::Duration;
   5
   6use db::kvp::KEY_VALUE_STORE;
   7use markdown::Markdown;
   8use serde::{Deserialize, Serialize};
   9
  10use anyhow::{Result, anyhow};
  11use assistant_context_editor::{
  12    AgentPanelDelegate, AssistantContext, ConfigurationError, ContextEditor, ContextEvent,
  13    SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
  14    render_remaining_tokens,
  15};
  16use assistant_settings::{AssistantDockPosition, AssistantSettings};
  17use assistant_slash_command::SlashCommandWorkingSet;
  18use assistant_tool::ToolWorkingSet;
  19
  20use client::{UserStore, zed_urls};
  21use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  22use fs::Fs;
  23use gpui::{
  24    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  25    Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, FontWeight,
  26    KeyContext, Pixels, Subscription, Task, UpdateGlobal, WeakEntity, linear_color_stop,
  27    linear_gradient, prelude::*, pulsating_between,
  28};
  29use language::LanguageRegistry;
  30use language_model::{
  31    LanguageModelProviderTosView, LanguageModelRegistry, RequestUsage, ZED_CLOUD_PROVIDER_ID,
  32};
  33use language_model_selector::ToggleModelSelector;
  34use project::{Project, ProjectPath, Worktree};
  35use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  36use proto::Plan;
  37use rules_library::{RulesLibrary, open_rules_library};
  38use search::{BufferSearchBar, buffer_search};
  39use settings::{Settings, update_settings_file};
  40use theme::ThemeSettings;
  41use time::UtcOffset;
  42use ui::utils::WithRemSize;
  43use ui::{
  44    Banner, CheckboxWithLabel, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle,
  45    ProgressBar, Tab, Tooltip, Vector, VectorName, prelude::*,
  46};
  47use util::{ResultExt as _, maybe};
  48use workspace::dock::{DockPosition, Panel, PanelEvent};
  49use workspace::{
  50    CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
  51};
  52use zed_actions::agent::{OpenConfiguration, OpenOnboardingModal, ResetOnboarding};
  53use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
  54use zed_actions::{DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize};
  55use zed_llm_client::UsageLimit;
  56
  57use crate::active_thread::{self, ActiveThread, ActiveThreadEvent};
  58use crate::agent_configuration::{AgentConfiguration, AssistantConfigurationEvent};
  59use crate::agent_diff::AgentDiff;
  60use crate::history_store::{HistoryStore, RecentEntry};
  61use crate::message_editor::{MessageEditor, MessageEditorEvent};
  62use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
  63use crate::thread_history::{HistoryEntryElement, ThreadHistory};
  64use crate::thread_store::ThreadStore;
  65use crate::ui::AgentOnboardingModal;
  66use crate::{
  67    AddContextServer, AgentDiffPane, ContextStore, DeleteRecentlyOpenThread, ExpandMessageEditor,
  68    Follow, InlineAssistant, NewTextThread, NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff,
  69    OpenHistory, ResetTrialUpsell, TextThreadStore, ThreadEvent, ToggleContextPicker,
  70    ToggleNavigationMenu, ToggleOptionsMenu,
  71};
  72
  73const AGENT_PANEL_KEY: &str = "agent_panel";
  74
  75#[derive(Serialize, Deserialize)]
  76struct SerializedAgentPanel {
  77    width: Option<Pixels>,
  78}
  79
  80pub fn init(cx: &mut App) {
  81    cx.observe_new(
  82        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  83            workspace
  84                .register_action(|workspace, action: &NewThread, window, cx| {
  85                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
  86                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
  87                        workspace.focus_panel::<AgentPanel>(window, cx);
  88                    }
  89                })
  90                .register_action(|workspace, _: &OpenHistory, window, cx| {
  91                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
  92                        workspace.focus_panel::<AgentPanel>(window, cx);
  93                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
  94                    }
  95                })
  96                .register_action(|workspace, _: &OpenConfiguration, window, cx| {
  97                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
  98                        workspace.focus_panel::<AgentPanel>(window, cx);
  99                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
 100                    }
 101                })
 102                .register_action(|workspace, _: &NewTextThread, window, cx| {
 103                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 104                        workspace.focus_panel::<AgentPanel>(window, cx);
 105                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
 106                    }
 107                })
 108                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
 109                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 110                        workspace.focus_panel::<AgentPanel>(window, cx);
 111                        panel.update(cx, |panel, cx| {
 112                            panel.deploy_rules_library(action, window, cx)
 113                        });
 114                    }
 115                })
 116                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
 117                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 118                        workspace.focus_panel::<AgentPanel>(window, cx);
 119                        let thread = panel.read(cx).thread.read(cx).thread().clone();
 120                        AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
 121                    }
 122                })
 123                .register_action(|workspace, _: &Follow, window, cx| {
 124                    workspace.follow(CollaboratorId::Agent, window, cx);
 125                })
 126                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
 127                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 128                        workspace.focus_panel::<AgentPanel>(window, cx);
 129                        panel.update(cx, |panel, cx| {
 130                            panel.message_editor.update(cx, |editor, cx| {
 131                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
 132                            });
 133                        });
 134                    }
 135                })
 136                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
 137                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 138                        workspace.focus_panel::<AgentPanel>(window, cx);
 139                        panel.update(cx, |panel, cx| {
 140                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
 141                        });
 142                    }
 143                })
 144                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
 145                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 146                        workspace.focus_panel::<AgentPanel>(window, cx);
 147                        panel.update(cx, |panel, cx| {
 148                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);
 149                        });
 150                    }
 151                })
 152                .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
 153                    AgentOnboardingModal::toggle(workspace, window, cx)
 154                })
 155                .register_action(|_workspace, _: &ResetOnboarding, window, cx| {
 156                    window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
 157                    window.refresh();
 158                })
 159                .register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
 160                    set_trial_upsell_dismissed(false, cx);
 161                });
 162        },
 163    )
 164    .detach();
 165}
 166
 167enum ActiveView {
 168    Thread {
 169        change_title_editor: Entity<Editor>,
 170        thread: WeakEntity<Thread>,
 171        _subscriptions: Vec<gpui::Subscription>,
 172    },
 173    PromptEditor {
 174        context_editor: Entity<ContextEditor>,
 175        title_editor: Entity<Editor>,
 176        buffer_search_bar: Entity<BufferSearchBar>,
 177        _subscriptions: Vec<gpui::Subscription>,
 178    },
 179    History,
 180    Configuration,
 181}
 182
 183enum WhichFontSize {
 184    AgentFont,
 185    BufferFont,
 186    None,
 187}
 188
 189impl ActiveView {
 190    pub fn which_font_size_used(&self) -> WhichFontSize {
 191        match self {
 192            ActiveView::Thread { .. } | ActiveView::History => WhichFontSize::AgentFont,
 193            ActiveView::PromptEditor { .. } => WhichFontSize::BufferFont,
 194            ActiveView::Configuration => WhichFontSize::None,
 195        }
 196    }
 197
 198    pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {
 199        let summary = thread.read(cx).summary_or_default();
 200
 201        let editor = cx.new(|cx| {
 202            let mut editor = Editor::single_line(window, cx);
 203            editor.set_text(summary.clone(), window, cx);
 204            editor
 205        });
 206
 207        let subscriptions = vec![
 208            window.subscribe(&editor, cx, {
 209                {
 210                    let thread = thread.clone();
 211                    move |editor, event, window, cx| match event {
 212                        EditorEvent::BufferEdited => {
 213                            let new_summary = editor.read(cx).text(cx);
 214
 215                            thread.update(cx, |thread, cx| {
 216                                thread.set_summary(new_summary, cx);
 217                            })
 218                        }
 219                        EditorEvent::Blurred => {
 220                            if editor.read(cx).text(cx).is_empty() {
 221                                let summary = thread.read(cx).summary_or_default();
 222
 223                                editor.update(cx, |editor, cx| {
 224                                    editor.set_text(summary, window, cx);
 225                                });
 226                            }
 227                        }
 228                        _ => {}
 229                    }
 230                }
 231            }),
 232            window.subscribe(&thread, cx, {
 233                let editor = editor.clone();
 234                move |thread, event, window, cx| match event {
 235                    ThreadEvent::SummaryGenerated => {
 236                        let summary = thread.read(cx).summary_or_default();
 237
 238                        editor.update(cx, |editor, cx| {
 239                            editor.set_text(summary, window, cx);
 240                        })
 241                    }
 242                    _ => {}
 243                }
 244            }),
 245        ];
 246
 247        Self::Thread {
 248            change_title_editor: editor,
 249            thread: thread.downgrade(),
 250            _subscriptions: subscriptions,
 251        }
 252    }
 253
 254    pub fn prompt_editor(
 255        context_editor: Entity<ContextEditor>,
 256        language_registry: Arc<LanguageRegistry>,
 257        window: &mut Window,
 258        cx: &mut App,
 259    ) -> Self {
 260        let title = context_editor.read(cx).title(cx).to_string();
 261
 262        let editor = cx.new(|cx| {
 263            let mut editor = Editor::single_line(window, cx);
 264            editor.set_text(title, window, cx);
 265            editor
 266        });
 267
 268        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 269        // cause a custom summary to be set. The presence of this custom summary would cause
 270        // summarization to not happen.
 271        let mut suppress_first_edit = true;
 272
 273        let subscriptions = vec![
 274            window.subscribe(&editor, cx, {
 275                {
 276                    let context_editor = context_editor.clone();
 277                    move |editor, event, window, cx| match event {
 278                        EditorEvent::BufferEdited => {
 279                            if suppress_first_edit {
 280                                suppress_first_edit = false;
 281                                return;
 282                            }
 283                            let new_summary = editor.read(cx).text(cx);
 284
 285                            context_editor.update(cx, |context_editor, cx| {
 286                                context_editor
 287                                    .context()
 288                                    .update(cx, |assistant_context, cx| {
 289                                        assistant_context.set_custom_summary(new_summary, cx);
 290                                    })
 291                            })
 292                        }
 293                        EditorEvent::Blurred => {
 294                            if editor.read(cx).text(cx).is_empty() {
 295                                let summary = context_editor
 296                                    .read(cx)
 297                                    .context()
 298                                    .read(cx)
 299                                    .summary_or_default();
 300
 301                                editor.update(cx, |editor, cx| {
 302                                    editor.set_text(summary, window, cx);
 303                                });
 304                            }
 305                        }
 306                        _ => {}
 307                    }
 308                }
 309            }),
 310            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 311                let editor = editor.clone();
 312                move |assistant_context, event, window, cx| match event {
 313                    ContextEvent::SummaryGenerated => {
 314                        let summary = assistant_context.read(cx).summary_or_default();
 315
 316                        editor.update(cx, |editor, cx| {
 317                            editor.set_text(summary, window, cx);
 318                        })
 319                    }
 320                    _ => {}
 321                }
 322            }),
 323        ];
 324
 325        let buffer_search_bar =
 326            cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
 327        buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 328            buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
 329        });
 330
 331        Self::PromptEditor {
 332            context_editor,
 333            title_editor: editor,
 334            buffer_search_bar,
 335            _subscriptions: subscriptions,
 336        }
 337    }
 338}
 339
 340pub struct AgentPanel {
 341    workspace: WeakEntity<Workspace>,
 342    user_store: Entity<UserStore>,
 343    project: Entity<Project>,
 344    fs: Arc<dyn Fs>,
 345    language_registry: Arc<LanguageRegistry>,
 346    thread_store: Entity<ThreadStore>,
 347    thread: Entity<ActiveThread>,
 348    message_editor: Entity<MessageEditor>,
 349    _active_thread_subscriptions: Vec<Subscription>,
 350    _default_model_subscription: Subscription,
 351    context_store: Entity<TextThreadStore>,
 352    prompt_store: Option<Entity<PromptStore>>,
 353    inline_assist_context_store: Entity<crate::context_store::ContextStore>,
 354    configuration: Option<Entity<AgentConfiguration>>,
 355    configuration_subscription: Option<Subscription>,
 356    local_timezone: UtcOffset,
 357    active_view: ActiveView,
 358    previous_view: Option<ActiveView>,
 359    history_store: Entity<HistoryStore>,
 360    history: Entity<ThreadHistory>,
 361    hovered_recent_history_item: Option<usize>,
 362    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
 363    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
 364    assistant_navigation_menu: Option<Entity<ContextMenu>>,
 365    width: Option<Pixels>,
 366    height: Option<Pixels>,
 367    zoomed: bool,
 368    pending_serialization: Option<Task<Result<()>>>,
 369    hide_trial_upsell: bool,
 370    _trial_markdown: Entity<Markdown>,
 371}
 372
 373impl AgentPanel {
 374    fn serialize(&mut self, cx: &mut Context<Self>) {
 375        let width = self.width;
 376        self.pending_serialization = Some(cx.background_spawn(async move {
 377            KEY_VALUE_STORE
 378                .write_kvp(
 379                    AGENT_PANEL_KEY.into(),
 380                    serde_json::to_string(&SerializedAgentPanel { width })?,
 381                )
 382                .await?;
 383            anyhow::Ok(())
 384        }));
 385    }
 386    pub fn load(
 387        workspace: WeakEntity<Workspace>,
 388        prompt_builder: Arc<PromptBuilder>,
 389        mut cx: AsyncWindowContext,
 390    ) -> Task<Result<Entity<Self>>> {
 391        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 392        cx.spawn(async move |cx| {
 393            let prompt_store = match prompt_store {
 394                Ok(prompt_store) => prompt_store.await.ok(),
 395                Err(_) => None,
 396            };
 397            let tools = cx.new(|_| ToolWorkingSet::default())?;
 398            let thread_store = workspace
 399                .update(cx, |workspace, cx| {
 400                    let project = workspace.project().clone();
 401                    ThreadStore::load(
 402                        project,
 403                        tools.clone(),
 404                        prompt_store.clone(),
 405                        prompt_builder.clone(),
 406                        cx,
 407                    )
 408                })?
 409                .await?;
 410
 411            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 412            let context_store = workspace
 413                .update(cx, |workspace, cx| {
 414                    let project = workspace.project().clone();
 415                    assistant_context_editor::ContextStore::new(
 416                        project,
 417                        prompt_builder.clone(),
 418                        slash_commands,
 419                        cx,
 420                    )
 421                })?
 422                .await?;
 423
 424            let serialized_panel = if let Some(panel) = cx
 425                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
 426                .await
 427                .log_err()
 428                .flatten()
 429            {
 430                Some(serde_json::from_str::<SerializedAgentPanel>(&panel)?)
 431            } else {
 432                None
 433            };
 434
 435            let panel = workspace.update_in(cx, |workspace, window, cx| {
 436                let panel = cx.new(|cx| {
 437                    Self::new(
 438                        workspace,
 439                        thread_store,
 440                        context_store,
 441                        prompt_store,
 442                        window,
 443                        cx,
 444                    )
 445                });
 446                if let Some(serialized_panel) = serialized_panel {
 447                    panel.update(cx, |panel, cx| {
 448                        panel.width = serialized_panel.width.map(|w| w.round());
 449                        cx.notify();
 450                    });
 451                }
 452                panel
 453            })?;
 454
 455            Ok(panel)
 456        })
 457    }
 458
 459    fn new(
 460        workspace: &Workspace,
 461        thread_store: Entity<ThreadStore>,
 462        context_store: Entity<TextThreadStore>,
 463        prompt_store: Option<Entity<PromptStore>>,
 464        window: &mut Window,
 465        cx: &mut Context<Self>,
 466    ) -> Self {
 467        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 468        let fs = workspace.app_state().fs.clone();
 469        let user_store = workspace.app_state().user_store.clone();
 470        let project = workspace.project();
 471        let language_registry = project.read(cx).languages().clone();
 472        let workspace = workspace.weak_handle();
 473        let weak_self = cx.entity().downgrade();
 474
 475        let message_editor_context_store = cx.new(|_cx| {
 476            crate::context_store::ContextStore::new(
 477                project.downgrade(),
 478                Some(thread_store.downgrade()),
 479            )
 480        });
 481        let inline_assist_context_store = cx.new(|_cx| {
 482            crate::context_store::ContextStore::new(
 483                project.downgrade(),
 484                Some(thread_store.downgrade()),
 485            )
 486        });
 487
 488        let message_editor = cx.new(|cx| {
 489            MessageEditor::new(
 490                fs.clone(),
 491                workspace.clone(),
 492                user_store.clone(),
 493                message_editor_context_store.clone(),
 494                prompt_store.clone(),
 495                thread_store.downgrade(),
 496                context_store.downgrade(),
 497                thread.clone(),
 498                window,
 499                cx,
 500            )
 501        });
 502
 503        let message_editor_subscription =
 504            cx.subscribe(&message_editor, |_, _, event, cx| match event {
 505                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 506                    cx.notify();
 507                }
 508            });
 509
 510        let thread_id = thread.read(cx).id().clone();
 511        let history_store = cx.new(|cx| {
 512            HistoryStore::new(
 513                thread_store.clone(),
 514                context_store.clone(),
 515                [RecentEntry::Thread(thread_id, thread.clone())],
 516                window,
 517                cx,
 518            )
 519        });
 520
 521        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 522
 523        let active_view = ActiveView::thread(thread.clone(), window, cx);
 524        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 525            if let ThreadEvent::MessageAdded(_) = &event {
 526                // needed to leave empty state
 527                cx.notify();
 528            }
 529        });
 530        let active_thread = cx.new(|cx| {
 531            ActiveThread::new(
 532                thread.clone(),
 533                thread_store.clone(),
 534                context_store.clone(),
 535                message_editor_context_store.clone(),
 536                language_registry.clone(),
 537                workspace.clone(),
 538                window,
 539                cx,
 540            )
 541        });
 542        AgentDiff::set_active_thread(&workspace, &thread, window, cx);
 543
 544        let active_thread_subscription =
 545            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
 546                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 547                    cx.notify();
 548                }
 549            });
 550
 551        let weak_panel = weak_self.clone();
 552
 553        window.defer(cx, move |window, cx| {
 554            let panel = weak_panel.clone();
 555            let assistant_navigation_menu =
 556                ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 557                    let recently_opened = panel
 558                        .update(cx, |this, cx| {
 559                            this.history_store.update(cx, |history_store, cx| {
 560                                history_store.recently_opened_entries(cx)
 561                            })
 562                        })
 563                        .unwrap_or_default();
 564
 565                    if !recently_opened.is_empty() {
 566                        menu = menu.header("Recently Opened");
 567
 568                        for entry in recently_opened.iter() {
 569                            let summary = entry.summary(cx);
 570
 571                            menu = menu.entry_with_end_slot_on_hover(
 572                                summary,
 573                                None,
 574                                {
 575                                    let panel = panel.clone();
 576                                    let entry = entry.clone();
 577                                    move |window, cx| {
 578                                        panel
 579                                            .update(cx, {
 580                                                let entry = entry.clone();
 581                                                move |this, cx| match entry {
 582                                                    RecentEntry::Thread(_, thread) => {
 583                                                        this.open_thread(thread, window, cx)
 584                                                    }
 585                                                    RecentEntry::Context(context) => {
 586                                                        let Some(path) = context.read(cx).path()
 587                                                        else {
 588                                                            return;
 589                                                        };
 590                                                        this.open_saved_prompt_editor(
 591                                                            path.clone(),
 592                                                            window,
 593                                                            cx,
 594                                                        )
 595                                                        .detach_and_log_err(cx)
 596                                                    }
 597                                                }
 598                                            })
 599                                            .ok();
 600                                    }
 601                                },
 602                                IconName::Close,
 603                                "Close Entry".into(),
 604                                {
 605                                    let panel = panel.clone();
 606                                    let entry = entry.clone();
 607                                    move |_window, cx| {
 608                                        panel
 609                                            .update(cx, |this, cx| {
 610                                                this.history_store.update(
 611                                                    cx,
 612                                                    |history_store, cx| {
 613                                                        history_store.remove_recently_opened_entry(
 614                                                            &entry, cx,
 615                                                        );
 616                                                    },
 617                                                );
 618                                            })
 619                                            .ok();
 620                                    }
 621                                },
 622                            );
 623                        }
 624
 625                        menu = menu.separator();
 626                    }
 627
 628                    menu.action("View All", Box::new(OpenHistory))
 629                        .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
 630                        .fixed_width(px(320.).into())
 631                        .keep_open_on_confirm(false)
 632                        .key_context("NavigationMenu")
 633                });
 634            weak_panel
 635                .update(cx, |panel, cx| {
 636                    cx.subscribe_in(
 637                        &assistant_navigation_menu,
 638                        window,
 639                        |_, menu, _: &DismissEvent, window, cx| {
 640                            menu.update(cx, |menu, _| {
 641                                menu.clear_selected();
 642                            });
 643                            cx.focus_self(window);
 644                        },
 645                    )
 646                    .detach();
 647                    panel.assistant_navigation_menu = Some(assistant_navigation_menu);
 648                })
 649                .ok();
 650        });
 651
 652        let _default_model_subscription = cx.subscribe(
 653            &LanguageModelRegistry::global(cx),
 654            |this, _, event: &language_model::Event, cx| match event {
 655                language_model::Event::DefaultModelChanged => {
 656                    this.thread
 657                        .read(cx)
 658                        .thread()
 659                        .clone()
 660                        .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
 661                }
 662                _ => {}
 663            },
 664        );
 665
 666        let trial_markdown = cx.new(|cx| {
 667            Markdown::new(
 668                include_str!("trial_markdown.md").into(),
 669                Some(language_registry.clone()),
 670                None,
 671                cx,
 672            )
 673        });
 674
 675        Self {
 676            active_view,
 677            workspace,
 678            user_store,
 679            project: project.clone(),
 680            fs: fs.clone(),
 681            language_registry,
 682            thread_store: thread_store.clone(),
 683            thread: active_thread,
 684            message_editor,
 685            _active_thread_subscriptions: vec![
 686                thread_subscription,
 687                active_thread_subscription,
 688                message_editor_subscription,
 689            ],
 690            _default_model_subscription,
 691            context_store,
 692            prompt_store,
 693            configuration: None,
 694            configuration_subscription: None,
 695            local_timezone: UtcOffset::from_whole_seconds(
 696                chrono::Local::now().offset().local_minus_utc(),
 697            )
 698            .unwrap(),
 699            inline_assist_context_store,
 700            previous_view: None,
 701            history_store: history_store.clone(),
 702            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 703            hovered_recent_history_item: None,
 704            assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
 705            assistant_navigation_menu_handle: PopoverMenuHandle::default(),
 706            assistant_navigation_menu: None,
 707            width: None,
 708            height: None,
 709            zoomed: false,
 710            pending_serialization: None,
 711            hide_trial_upsell: false,
 712            _trial_markdown: trial_markdown,
 713        }
 714    }
 715
 716    pub fn toggle_focus(
 717        workspace: &mut Workspace,
 718        _: &ToggleFocus,
 719        window: &mut Window,
 720        cx: &mut Context<Workspace>,
 721    ) {
 722        if workspace
 723            .panel::<Self>(cx)
 724            .is_some_and(|panel| panel.read(cx).enabled(cx))
 725        {
 726            workspace.toggle_panel_focus::<Self>(window, cx);
 727        }
 728    }
 729
 730    pub(crate) fn local_timezone(&self) -> UtcOffset {
 731        self.local_timezone
 732    }
 733
 734    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 735        &self.prompt_store
 736    }
 737
 738    pub(crate) fn inline_assist_context_store(
 739        &self,
 740    ) -> &Entity<crate::context_store::ContextStore> {
 741        &self.inline_assist_context_store
 742    }
 743
 744    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 745        &self.thread_store
 746    }
 747
 748    pub(crate) fn text_thread_store(&self) -> &Entity<TextThreadStore> {
 749        &self.context_store
 750    }
 751
 752    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 753        self.thread
 754            .update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 755    }
 756
 757    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 758        let thread = self
 759            .thread_store
 760            .update(cx, |this, cx| this.create_thread(cx));
 761
 762        let thread_view = ActiveView::thread(thread.clone(), window, cx);
 763        self.set_active_view(thread_view, window, cx);
 764
 765        let context_store = cx.new(|_cx| {
 766            crate::context_store::ContextStore::new(
 767                self.project.downgrade(),
 768                Some(self.thread_store.downgrade()),
 769            )
 770        });
 771
 772        if let Some(other_thread_id) = action.from_thread_id.clone() {
 773            let other_thread_task = self.thread_store.update(cx, |this, cx| {
 774                this.open_thread(&other_thread_id, window, cx)
 775            });
 776
 777            cx.spawn({
 778                let context_store = context_store.clone();
 779
 780                async move |_panel, cx| {
 781                    let other_thread = other_thread_task.await?;
 782
 783                    context_store.update(cx, |this, cx| {
 784                        this.add_thread(other_thread, false, cx);
 785                    })?;
 786                    anyhow::Ok(())
 787                }
 788            })
 789            .detach_and_log_err(cx);
 790        }
 791
 792        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 793            if let ThreadEvent::MessageAdded(_) = &event {
 794                // needed to leave empty state
 795                cx.notify();
 796            }
 797        });
 798
 799        self.thread = cx.new(|cx| {
 800            ActiveThread::new(
 801                thread.clone(),
 802                self.thread_store.clone(),
 803                self.context_store.clone(),
 804                context_store.clone(),
 805                self.language_registry.clone(),
 806                self.workspace.clone(),
 807                window,
 808                cx,
 809            )
 810        });
 811        AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
 812
 813        let active_thread_subscription =
 814            cx.subscribe(&self.thread, |_, _, event, cx| match &event {
 815                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 816                    cx.notify();
 817                }
 818            });
 819
 820        self.message_editor = cx.new(|cx| {
 821            MessageEditor::new(
 822                self.fs.clone(),
 823                self.workspace.clone(),
 824                self.user_store.clone(),
 825                context_store,
 826                self.prompt_store.clone(),
 827                self.thread_store.downgrade(),
 828                self.context_store.downgrade(),
 829                thread,
 830                window,
 831                cx,
 832            )
 833        });
 834        self.message_editor.focus_handle(cx).focus(window);
 835
 836        let message_editor_subscription =
 837            cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
 838                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 839                    cx.notify();
 840                }
 841            });
 842
 843        self._active_thread_subscriptions = vec![
 844            thread_subscription,
 845            active_thread_subscription,
 846            message_editor_subscription,
 847        ];
 848    }
 849
 850    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 851        let context = self
 852            .context_store
 853            .update(cx, |context_store, cx| context_store.create(cx));
 854        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 855            .log_err()
 856            .flatten();
 857
 858        let context_editor = cx.new(|cx| {
 859            let mut editor = ContextEditor::for_context(
 860                context,
 861                self.fs.clone(),
 862                self.workspace.clone(),
 863                self.project.clone(),
 864                lsp_adapter_delegate,
 865                window,
 866                cx,
 867            );
 868            editor.insert_default_prompt(window, cx);
 869            editor
 870        });
 871
 872        self.set_active_view(
 873            ActiveView::prompt_editor(
 874                context_editor.clone(),
 875                self.language_registry.clone(),
 876                window,
 877                cx,
 878            ),
 879            window,
 880            cx,
 881        );
 882        context_editor.focus_handle(cx).focus(window);
 883    }
 884
 885    fn deploy_rules_library(
 886        &mut self,
 887        action: &OpenRulesLibrary,
 888        _window: &mut Window,
 889        cx: &mut Context<Self>,
 890    ) {
 891        open_rules_library(
 892            self.language_registry.clone(),
 893            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
 894            Arc::new(|| {
 895                Box::new(SlashCommandCompletionProvider::new(
 896                    Arc::new(SlashCommandWorkingSet::default()),
 897                    None,
 898                    None,
 899                ))
 900            }),
 901            action
 902                .prompt_to_select
 903                .map(|uuid| UserPromptId(uuid).into()),
 904            cx,
 905        )
 906        .detach_and_log_err(cx);
 907    }
 908
 909    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 910        if matches!(self.active_view, ActiveView::History) {
 911            if let Some(previous_view) = self.previous_view.take() {
 912                self.set_active_view(previous_view, window, cx);
 913            }
 914        } else {
 915            self.thread_store
 916                .update(cx, |thread_store, cx| thread_store.reload(cx))
 917                .detach_and_log_err(cx);
 918            self.set_active_view(ActiveView::History, window, cx);
 919        }
 920        cx.notify();
 921    }
 922
 923    pub(crate) fn open_saved_prompt_editor(
 924        &mut self,
 925        path: Arc<Path>,
 926        window: &mut Window,
 927        cx: &mut Context<Self>,
 928    ) -> Task<Result<()>> {
 929        let context = self
 930            .context_store
 931            .update(cx, |store, cx| store.open_local_context(path, cx));
 932        cx.spawn_in(window, async move |this, cx| {
 933            let context = context.await?;
 934            this.update_in(cx, |this, window, cx| {
 935                this.open_prompt_editor(context, window, cx);
 936            })
 937        })
 938    }
 939
 940    pub(crate) fn open_prompt_editor(
 941        &mut self,
 942        context: Entity<AssistantContext>,
 943        window: &mut Window,
 944        cx: &mut Context<Self>,
 945    ) {
 946        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
 947            .log_err()
 948            .flatten();
 949        let editor = cx.new(|cx| {
 950            ContextEditor::for_context(
 951                context,
 952                self.fs.clone(),
 953                self.workspace.clone(),
 954                self.project.clone(),
 955                lsp_adapter_delegate,
 956                window,
 957                cx,
 958            )
 959        });
 960        self.set_active_view(
 961            ActiveView::prompt_editor(editor.clone(), self.language_registry.clone(), window, cx),
 962            window,
 963            cx,
 964        );
 965    }
 966
 967    pub(crate) fn open_thread_by_id(
 968        &mut self,
 969        thread_id: &ThreadId,
 970        window: &mut Window,
 971        cx: &mut Context<Self>,
 972    ) -> Task<Result<()>> {
 973        let open_thread_task = self
 974            .thread_store
 975            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
 976        cx.spawn_in(window, async move |this, cx| {
 977            let thread = open_thread_task.await?;
 978            this.update_in(cx, |this, window, cx| {
 979                this.open_thread(thread, window, cx);
 980                anyhow::Ok(())
 981            })??;
 982            Ok(())
 983        })
 984    }
 985
 986    pub(crate) fn open_thread(
 987        &mut self,
 988        thread: Entity<Thread>,
 989        window: &mut Window,
 990        cx: &mut Context<Self>,
 991    ) {
 992        let thread_view = ActiveView::thread(thread.clone(), window, cx);
 993        self.set_active_view(thread_view, window, cx);
 994        let context_store = cx.new(|_cx| {
 995            crate::context_store::ContextStore::new(
 996                self.project.downgrade(),
 997                Some(self.thread_store.downgrade()),
 998            )
 999        });
1000        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
1001            if let ThreadEvent::MessageAdded(_) = &event {
1002                // needed to leave empty state
1003                cx.notify();
1004            }
1005        });
1006
1007        self.thread = cx.new(|cx| {
1008            ActiveThread::new(
1009                thread.clone(),
1010                self.thread_store.clone(),
1011                self.context_store.clone(),
1012                context_store.clone(),
1013                self.language_registry.clone(),
1014                self.workspace.clone(),
1015                window,
1016                cx,
1017            )
1018        });
1019        AgentDiff::set_active_thread(&self.workspace, &thread, window, cx);
1020
1021        let active_thread_subscription =
1022            cx.subscribe(&self.thread, |_, _, event, cx| match &event {
1023                ActiveThreadEvent::EditingMessageTokenCountChanged => {
1024                    cx.notify();
1025                }
1026            });
1027
1028        self.message_editor = cx.new(|cx| {
1029            MessageEditor::new(
1030                self.fs.clone(),
1031                self.workspace.clone(),
1032                self.user_store.clone(),
1033                context_store,
1034                self.prompt_store.clone(),
1035                self.thread_store.downgrade(),
1036                self.context_store.downgrade(),
1037                thread,
1038                window,
1039                cx,
1040            )
1041        });
1042        self.message_editor.focus_handle(cx).focus(window);
1043
1044        let message_editor_subscription =
1045            cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
1046                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
1047                    cx.notify();
1048                }
1049            });
1050
1051        self._active_thread_subscriptions = vec![
1052            thread_subscription,
1053            active_thread_subscription,
1054            message_editor_subscription,
1055        ];
1056    }
1057
1058    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1059        match self.active_view {
1060            ActiveView::Configuration | ActiveView::History => {
1061                self.active_view =
1062                    ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
1063                self.message_editor.focus_handle(cx).focus(window);
1064                cx.notify();
1065            }
1066            _ => {}
1067        }
1068    }
1069
1070    pub fn toggle_navigation_menu(
1071        &mut self,
1072        _: &ToggleNavigationMenu,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) {
1076        self.assistant_navigation_menu_handle.toggle(window, cx);
1077    }
1078
1079    pub fn toggle_options_menu(
1080        &mut self,
1081        _: &ToggleOptionsMenu,
1082        window: &mut Window,
1083        cx: &mut Context<Self>,
1084    ) {
1085        self.assistant_dropdown_menu_handle.toggle(window, cx);
1086    }
1087
1088    pub fn increase_font_size(
1089        &mut self,
1090        action: &IncreaseBufferFontSize,
1091        _: &mut Window,
1092        cx: &mut Context<Self>,
1093    ) {
1094        self.handle_font_size_action(action.persist, px(1.0), cx);
1095    }
1096
1097    pub fn decrease_font_size(
1098        &mut self,
1099        action: &DecreaseBufferFontSize,
1100        _: &mut Window,
1101        cx: &mut Context<Self>,
1102    ) {
1103        self.handle_font_size_action(action.persist, px(-1.0), cx);
1104    }
1105
1106    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1107        match self.active_view.which_font_size_used() {
1108            WhichFontSize::AgentFont => {
1109                if persist {
1110                    update_settings_file::<ThemeSettings>(
1111                        self.fs.clone(),
1112                        cx,
1113                        move |settings, cx| {
1114                            let agent_font_size =
1115                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1116                            let _ = settings
1117                                .agent_font_size
1118                                .insert(theme::clamp_font_size(agent_font_size).0);
1119                        },
1120                    );
1121                } else {
1122                    theme::adjust_agent_font_size(cx, |size| {
1123                        *size += delta;
1124                    });
1125                }
1126            }
1127            WhichFontSize::BufferFont => {
1128                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1129                // default handler that changes that font size.
1130                cx.propagate();
1131            }
1132            WhichFontSize::None => {}
1133        }
1134    }
1135
1136    pub fn reset_font_size(
1137        &mut self,
1138        action: &ResetBufferFontSize,
1139        _: &mut Window,
1140        cx: &mut Context<Self>,
1141    ) {
1142        if action.persist {
1143            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1144                settings.agent_font_size = None;
1145            });
1146        } else {
1147            theme::reset_agent_font_size(cx);
1148        }
1149    }
1150
1151    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1152        if self.zoomed {
1153            cx.emit(PanelEvent::ZoomOut);
1154        } else {
1155            if !self.focus_handle(cx).contains_focused(window, cx) {
1156                cx.focus_self(window);
1157            }
1158            cx.emit(PanelEvent::ZoomIn);
1159        }
1160    }
1161
1162    pub fn open_agent_diff(
1163        &mut self,
1164        _: &OpenAgentDiff,
1165        window: &mut Window,
1166        cx: &mut Context<Self>,
1167    ) {
1168        let thread = self.thread.read(cx).thread().clone();
1169        self.workspace
1170            .update(cx, |workspace, cx| {
1171                AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx)
1172            })
1173            .log_err();
1174    }
1175
1176    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1177        let context_server_store = self.project.read(cx).context_server_store();
1178        let tools = self.thread_store.read(cx).tools();
1179        let fs = self.fs.clone();
1180
1181        self.set_active_view(ActiveView::Configuration, window, cx);
1182        self.configuration =
1183            Some(cx.new(|cx| AgentConfiguration::new(fs, context_server_store, tools, window, cx)));
1184
1185        if let Some(configuration) = self.configuration.as_ref() {
1186            self.configuration_subscription = Some(cx.subscribe_in(
1187                configuration,
1188                window,
1189                Self::handle_agent_configuration_event,
1190            ));
1191
1192            configuration.focus_handle(cx).focus(window);
1193        }
1194    }
1195
1196    pub(crate) fn open_active_thread_as_markdown(
1197        &mut self,
1198        _: &OpenActiveThreadAsMarkdown,
1199        window: &mut Window,
1200        cx: &mut Context<Self>,
1201    ) {
1202        let Some(workspace) = self
1203            .workspace
1204            .upgrade()
1205            .ok_or_else(|| anyhow!("workspace dropped"))
1206            .log_err()
1207        else {
1208            return;
1209        };
1210
1211        let Some(thread) = self.active_thread() else {
1212            return;
1213        };
1214
1215        active_thread::open_active_thread_as_markdown(thread, workspace, window, cx)
1216            .detach_and_log_err(cx);
1217    }
1218
1219    fn handle_agent_configuration_event(
1220        &mut self,
1221        _entity: &Entity<AgentConfiguration>,
1222        event: &AssistantConfigurationEvent,
1223        window: &mut Window,
1224        cx: &mut Context<Self>,
1225    ) {
1226        match event {
1227            AssistantConfigurationEvent::NewThread(provider) => {
1228                if LanguageModelRegistry::read_global(cx)
1229                    .default_model()
1230                    .map_or(true, |model| model.provider.id() != provider.id())
1231                {
1232                    if let Some(model) = provider.default_model(cx) {
1233                        update_settings_file::<AssistantSettings>(
1234                            self.fs.clone(),
1235                            cx,
1236                            move |settings, _| settings.set_model(model),
1237                        );
1238                    }
1239                }
1240
1241                self.new_thread(&NewThread::default(), window, cx);
1242            }
1243        }
1244    }
1245
1246    pub(crate) fn active_thread(&self) -> Option<Entity<Thread>> {
1247        match &self.active_view {
1248            ActiveView::Thread { thread, .. } => thread.upgrade(),
1249            _ => None,
1250        }
1251    }
1252
1253    pub(crate) fn delete_thread(
1254        &mut self,
1255        thread_id: &ThreadId,
1256        cx: &mut Context<Self>,
1257    ) -> Task<Result<()>> {
1258        self.thread_store
1259            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1260    }
1261
1262    pub(crate) fn has_active_thread(&self) -> bool {
1263        matches!(self.active_view, ActiveView::Thread { .. })
1264    }
1265
1266    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
1267        match &self.active_view {
1268            ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
1269            _ => None,
1270        }
1271    }
1272
1273    pub(crate) fn delete_context(
1274        &mut self,
1275        path: Arc<Path>,
1276        cx: &mut Context<Self>,
1277    ) -> Task<Result<()>> {
1278        self.context_store
1279            .update(cx, |this, cx| this.delete_local_context(path, cx))
1280    }
1281
1282    fn set_active_view(
1283        &mut self,
1284        new_view: ActiveView,
1285        window: &mut Window,
1286        cx: &mut Context<Self>,
1287    ) {
1288        let current_is_history = matches!(self.active_view, ActiveView::History);
1289        let new_is_history = matches!(new_view, ActiveView::History);
1290
1291        match &self.active_view {
1292            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1293                if let Some(thread) = thread.upgrade() {
1294                    if thread.read(cx).is_empty() {
1295                        let id = thread.read(cx).id().clone();
1296                        store.remove_recently_opened_thread(id, cx);
1297                    }
1298                }
1299            }),
1300            _ => {}
1301        }
1302
1303        match &new_view {
1304            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1305                if let Some(thread) = thread.upgrade() {
1306                    let id = thread.read(cx).id().clone();
1307                    store.push_recently_opened_entry(RecentEntry::Thread(id, thread), cx);
1308                }
1309            }),
1310            ActiveView::PromptEditor { context_editor, .. } => {
1311                self.history_store.update(cx, |store, cx| {
1312                    let context = context_editor.read(cx).context().clone();
1313                    store.push_recently_opened_entry(RecentEntry::Context(context), cx)
1314                })
1315            }
1316            _ => {}
1317        }
1318
1319        if current_is_history && !new_is_history {
1320            self.active_view = new_view;
1321        } else if !current_is_history && new_is_history {
1322            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1323        } else {
1324            if !new_is_history {
1325                self.previous_view = None;
1326            }
1327            self.active_view = new_view;
1328        }
1329
1330        self.focus_handle(cx).focus(window);
1331    }
1332}
1333
1334impl Focusable for AgentPanel {
1335    fn focus_handle(&self, cx: &App) -> FocusHandle {
1336        match &self.active_view {
1337            ActiveView::Thread { .. } => self.message_editor.focus_handle(cx),
1338            ActiveView::History => self.history.focus_handle(cx),
1339            ActiveView::PromptEditor { context_editor, .. } => context_editor.focus_handle(cx),
1340            ActiveView::Configuration => {
1341                if let Some(configuration) = self.configuration.as_ref() {
1342                    configuration.focus_handle(cx)
1343                } else {
1344                    cx.focus_handle()
1345                }
1346            }
1347        }
1348    }
1349}
1350
1351fn agent_panel_dock_position(cx: &App) -> DockPosition {
1352    match AssistantSettings::get_global(cx).dock {
1353        AssistantDockPosition::Left => DockPosition::Left,
1354        AssistantDockPosition::Bottom => DockPosition::Bottom,
1355        AssistantDockPosition::Right => DockPosition::Right,
1356    }
1357}
1358
1359impl EventEmitter<PanelEvent> for AgentPanel {}
1360
1361impl Panel for AgentPanel {
1362    fn persistent_name() -> &'static str {
1363        "AgentPanel"
1364    }
1365
1366    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1367        agent_panel_dock_position(cx)
1368    }
1369
1370    fn position_is_valid(&self, position: DockPosition) -> bool {
1371        position != DockPosition::Bottom
1372    }
1373
1374    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1375        settings::update_settings_file::<AssistantSettings>(
1376            self.fs.clone(),
1377            cx,
1378            move |settings, _| {
1379                let dock = match position {
1380                    DockPosition::Left => AssistantDockPosition::Left,
1381                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1382                    DockPosition::Right => AssistantDockPosition::Right,
1383                };
1384                settings.set_dock(dock);
1385            },
1386        );
1387    }
1388
1389    fn size(&self, window: &Window, cx: &App) -> Pixels {
1390        let settings = AssistantSettings::get_global(cx);
1391        match self.position(window, cx) {
1392            DockPosition::Left | DockPosition::Right => {
1393                self.width.unwrap_or(settings.default_width)
1394            }
1395            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1396        }
1397    }
1398
1399    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1400        match self.position(window, cx) {
1401            DockPosition::Left | DockPosition::Right => self.width = size,
1402            DockPosition::Bottom => self.height = size,
1403        }
1404        self.serialize(cx);
1405        cx.notify();
1406    }
1407
1408    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1409
1410    fn remote_id() -> Option<proto::PanelId> {
1411        Some(proto::PanelId::AssistantPanel)
1412    }
1413
1414    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1415        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
1416            .then_some(IconName::ZedAssistant)
1417    }
1418
1419    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1420        Some("Agent Panel")
1421    }
1422
1423    fn toggle_action(&self) -> Box<dyn Action> {
1424        Box::new(ToggleFocus)
1425    }
1426
1427    fn activation_priority(&self) -> u32 {
1428        3
1429    }
1430
1431    fn enabled(&self, cx: &App) -> bool {
1432        AssistantSettings::get_global(cx).enabled
1433    }
1434
1435    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1436        self.zoomed
1437    }
1438
1439    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1440        self.zoomed = zoomed;
1441        cx.notify();
1442    }
1443}
1444
1445impl AgentPanel {
1446    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1447        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1448
1449        let content = match &self.active_view {
1450            ActiveView::Thread {
1451                change_title_editor,
1452                ..
1453            } => {
1454                let active_thread = self.thread.read(cx);
1455                let is_empty = active_thread.is_empty();
1456
1457                let summary = active_thread.summary(cx);
1458
1459                if is_empty {
1460                    Label::new(Thread::DEFAULT_SUMMARY.clone())
1461                        .truncate()
1462                        .into_any_element()
1463                } else if summary.is_none() {
1464                    Label::new(LOADING_SUMMARY_PLACEHOLDER)
1465                        .truncate()
1466                        .into_any_element()
1467                } else {
1468                    div()
1469                        .w_full()
1470                        .child(change_title_editor.clone())
1471                        .into_any_element()
1472                }
1473            }
1474            ActiveView::PromptEditor {
1475                title_editor,
1476                context_editor,
1477                ..
1478            } => {
1479                let context_editor = context_editor.read(cx);
1480                let summary = context_editor.context().read(cx).summary();
1481
1482                match summary {
1483                    None => Label::new(AssistantContext::DEFAULT_SUMMARY.clone())
1484                        .truncate()
1485                        .into_any_element(),
1486                    Some(summary) => {
1487                        if summary.done {
1488                            div()
1489                                .w_full()
1490                                .child(title_editor.clone())
1491                                .into_any_element()
1492                        } else {
1493                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1494                                .truncate()
1495                                .into_any_element()
1496                        }
1497                    }
1498                }
1499            }
1500            ActiveView::History => Label::new("History").truncate().into_any_element(),
1501            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1502        };
1503
1504        h_flex()
1505            .key_context("TitleEditor")
1506            .id("TitleEditor")
1507            .flex_grow()
1508            .w_full()
1509            .max_w_full()
1510            .overflow_x_scroll()
1511            .child(content)
1512            .into_any()
1513    }
1514
1515    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1516        let active_thread = self.thread.read(cx);
1517        let user_store = self.user_store.read(cx);
1518        let thread = active_thread.thread().read(cx);
1519        let thread_id = thread.id().clone();
1520        let is_empty = active_thread.is_empty();
1521        let editor_empty = self.message_editor.read(cx).is_editor_fully_empty(cx);
1522        let last_usage = active_thread.thread().read(cx).last_usage().or_else(|| {
1523            maybe!({
1524                let amount = user_store.model_request_usage_amount()?;
1525                let limit = user_store.model_request_usage_limit()?.variant?;
1526
1527                Some(RequestUsage {
1528                    amount: amount as i32,
1529                    limit: match limit {
1530                        proto::usage_limit::Variant::Limited(limited) => {
1531                            zed_llm_client::UsageLimit::Limited(limited.limit as i32)
1532                        }
1533                        proto::usage_limit::Variant::Unlimited(_) => {
1534                            zed_llm_client::UsageLimit::Unlimited
1535                        }
1536                    },
1537                })
1538            })
1539        });
1540
1541        let account_url = zed_urls::account_url(cx);
1542
1543        let show_token_count = match &self.active_view {
1544            ActiveView::Thread { .. } => !is_empty || !editor_empty,
1545            ActiveView::PromptEditor { .. } => true,
1546            _ => false,
1547        };
1548
1549        let focus_handle = self.focus_handle(cx);
1550
1551        let go_back_button = div().child(
1552            IconButton::new("go-back", IconName::ArrowLeft)
1553                .icon_size(IconSize::Small)
1554                .on_click(cx.listener(|this, _, window, cx| {
1555                    this.go_back(&workspace::GoBack, window, cx);
1556                }))
1557                .tooltip({
1558                    let focus_handle = focus_handle.clone();
1559                    move |window, cx| {
1560                        Tooltip::for_action_in(
1561                            "Go Back",
1562                            &workspace::GoBack,
1563                            &focus_handle,
1564                            window,
1565                            cx,
1566                        )
1567                    }
1568                }),
1569        );
1570
1571        let recent_entries_menu = div().child(
1572            PopoverMenu::new("agent-nav-menu")
1573                .trigger_with_tooltip(
1574                    IconButton::new("agent-nav-menu", IconName::MenuAlt)
1575                        .icon_size(IconSize::Small)
1576                        .style(ui::ButtonStyle::Subtle),
1577                    {
1578                        let focus_handle = focus_handle.clone();
1579                        move |window, cx| {
1580                            Tooltip::for_action_in(
1581                                "Toggle Panel Menu",
1582                                &ToggleNavigationMenu,
1583                                &focus_handle,
1584                                window,
1585                                cx,
1586                            )
1587                        }
1588                    },
1589                )
1590                .anchor(Corner::TopLeft)
1591                .with_handle(self.assistant_navigation_menu_handle.clone())
1592                .menu({
1593                    let menu = self.assistant_navigation_menu.clone();
1594                    move |window, cx| {
1595                        if let Some(menu) = menu.as_ref() {
1596                            menu.update(cx, |_, cx| {
1597                                cx.defer_in(window, |menu, window, cx| {
1598                                    menu.rebuild(window, cx);
1599                                });
1600                            })
1601                        }
1602                        menu.clone()
1603                    }
1604                }),
1605        );
1606
1607        let agent_extra_menu = PopoverMenu::new("agent-options-menu")
1608            .trigger_with_tooltip(
1609                IconButton::new("agent-options-menu", IconName::Ellipsis)
1610                    .icon_size(IconSize::Small),
1611                {
1612                    let focus_handle = focus_handle.clone();
1613                    move |window, cx| {
1614                        Tooltip::for_action_in(
1615                            "Toggle Agent Menu",
1616                            &ToggleOptionsMenu,
1617                            &focus_handle,
1618                            window,
1619                            cx,
1620                        )
1621                    }
1622                },
1623            )
1624            .anchor(Corner::TopRight)
1625            .with_handle(self.assistant_dropdown_menu_handle.clone())
1626            .menu(move |window, cx| {
1627                Some(ContextMenu::build(window, cx, |mut menu, _window, _cx| {
1628                    menu = menu
1629                        .action("New Thread", NewThread::default().boxed_clone())
1630                        .action("New Text Thread", NewTextThread.boxed_clone())
1631                        .when(!is_empty, |menu| {
1632                            menu.action(
1633                                "New From Summary",
1634                                Box::new(NewThread {
1635                                    from_thread_id: Some(thread_id.clone()),
1636                                }),
1637                            )
1638                        })
1639                        .separator();
1640
1641                    menu = menu
1642                        .header("MCP Servers")
1643                        .action(
1644                            "View Server Extensions",
1645                            Box::new(zed_actions::Extensions {
1646                                category_filter: Some(
1647                                    zed_actions::ExtensionCategoryFilter::ContextServers,
1648                                ),
1649                            }),
1650                        )
1651                        .action("Add Custom Server…", Box::new(AddContextServer))
1652                        .separator();
1653
1654                    if let Some(usage) = last_usage {
1655                        menu = menu
1656                            .header_with_link("Prompt Usage", "Manage", account_url.clone())
1657                            .custom_entry(
1658                                move |_window, cx| {
1659                                    let used_percentage = match usage.limit {
1660                                        UsageLimit::Limited(limit) => {
1661                                            Some((usage.amount as f32 / limit as f32) * 100.)
1662                                        }
1663                                        UsageLimit::Unlimited => None,
1664                                    };
1665
1666                                    h_flex()
1667                                        .flex_1()
1668                                        .gap_1p5()
1669                                        .children(used_percentage.map(|percent| {
1670                                            ProgressBar::new("usage", percent, 100., cx)
1671                                        }))
1672                                        .child(
1673                                            Label::new(match usage.limit {
1674                                                UsageLimit::Limited(limit) => {
1675                                                    format!("{} / {limit}", usage.amount)
1676                                                }
1677                                                UsageLimit::Unlimited => {
1678                                                    format!("{} / ∞", usage.amount)
1679                                                }
1680                                            })
1681                                            .size(LabelSize::Small)
1682                                            .color(Color::Muted),
1683                                        )
1684                                        .into_any_element()
1685                                },
1686                                move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
1687                            )
1688                            .separator()
1689                    }
1690
1691                    menu = menu
1692                        .action("Rules…", Box::new(OpenRulesLibrary::default()))
1693                        .action("Settings", Box::new(OpenConfiguration));
1694                    menu
1695                }))
1696            });
1697
1698        h_flex()
1699            .id("assistant-toolbar")
1700            .h(Tab::container_height(cx))
1701            .max_w_full()
1702            .flex_none()
1703            .justify_between()
1704            .gap_2()
1705            .bg(cx.theme().colors().tab_bar_background)
1706            .border_b_1()
1707            .border_color(cx.theme().colors().border)
1708            .child(
1709                h_flex()
1710                    .size_full()
1711                    .pl_1()
1712                    .gap_1()
1713                    .child(match &self.active_view {
1714                        ActiveView::History | ActiveView::Configuration => go_back_button,
1715                        _ => recent_entries_menu,
1716                    })
1717                    .child(self.render_title_view(window, cx)),
1718            )
1719            .child(
1720                h_flex()
1721                    .h_full()
1722                    .gap_2()
1723                    .when(show_token_count, |parent| {
1724                        parent.children(self.render_token_count(&thread, cx))
1725                    })
1726                    .child(
1727                        h_flex()
1728                            .h_full()
1729                            .gap(DynamicSpacing::Base02.rems(cx))
1730                            .px(DynamicSpacing::Base08.rems(cx))
1731                            .border_l_1()
1732                            .border_color(cx.theme().colors().border)
1733                            .child(
1734                                IconButton::new("new", IconName::Plus)
1735                                    .icon_size(IconSize::Small)
1736                                    .style(ButtonStyle::Subtle)
1737                                    .tooltip(move |window, cx| {
1738                                        Tooltip::for_action_in(
1739                                            "New Thread",
1740                                            &NewThread::default(),
1741                                            &focus_handle,
1742                                            window,
1743                                            cx,
1744                                        )
1745                                    })
1746                                    .on_click(move |_event, window, cx| {
1747                                        window.dispatch_action(
1748                                            NewThread::default().boxed_clone(),
1749                                            cx,
1750                                        );
1751                                    }),
1752                            )
1753                            .child(agent_extra_menu),
1754                    ),
1755            )
1756    }
1757
1758    fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
1759        let is_generating = thread.is_generating();
1760        let message_editor = self.message_editor.read(cx);
1761
1762        let conversation_token_usage = thread.total_token_usage()?;
1763
1764        let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
1765            self.thread.read(cx).editing_message_id()
1766        {
1767            let combined = thread
1768                .token_usage_up_to_message(editing_message_id)
1769                .add(unsent_tokens);
1770
1771            (combined, unsent_tokens > 0)
1772        } else {
1773            let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
1774            let combined = conversation_token_usage.add(unsent_tokens);
1775
1776            (combined, unsent_tokens > 0)
1777        };
1778
1779        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
1780
1781        match &self.active_view {
1782            ActiveView::Thread { .. } => {
1783                if total_token_usage.total == 0 {
1784                    return None;
1785                }
1786
1787                let token_color = match total_token_usage.ratio() {
1788                    TokenUsageRatio::Normal if is_estimating => Color::Default,
1789                    TokenUsageRatio::Normal => Color::Muted,
1790                    TokenUsageRatio::Warning => Color::Warning,
1791                    TokenUsageRatio::Exceeded => Color::Error,
1792                };
1793
1794                let token_count = h_flex()
1795                    .id("token-count")
1796                    .flex_shrink_0()
1797                    .gap_0p5()
1798                    .when(!is_generating && is_estimating, |parent| {
1799                        parent
1800                            .child(
1801                                h_flex()
1802                                    .mr_1()
1803                                    .size_2p5()
1804                                    .justify_center()
1805                                    .rounded_full()
1806                                    .bg(cx.theme().colors().text.opacity(0.1))
1807                                    .child(
1808                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
1809                                    ),
1810                            )
1811                            .tooltip(move |window, cx| {
1812                                Tooltip::with_meta(
1813                                    "Estimated New Token Count",
1814                                    None,
1815                                    format!(
1816                                        "Current Conversation Tokens: {}",
1817                                        humanize_token_count(conversation_token_usage.total)
1818                                    ),
1819                                    window,
1820                                    cx,
1821                                )
1822                            })
1823                    })
1824                    .child(
1825                        Label::new(humanize_token_count(total_token_usage.total))
1826                            .size(LabelSize::Small)
1827                            .color(token_color)
1828                            .map(|label| {
1829                                if is_generating || is_waiting_to_update_token_count {
1830                                    label
1831                                        .with_animation(
1832                                            "used-tokens-label",
1833                                            Animation::new(Duration::from_secs(2))
1834                                                .repeat()
1835                                                .with_easing(pulsating_between(0.6, 1.)),
1836                                            |label, delta| label.alpha(delta),
1837                                        )
1838                                        .into_any()
1839                                } else {
1840                                    label.into_any_element()
1841                                }
1842                            }),
1843                    )
1844                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
1845                    .child(
1846                        Label::new(humanize_token_count(total_token_usage.max))
1847                            .size(LabelSize::Small)
1848                            .color(Color::Muted),
1849                    )
1850                    .into_any();
1851
1852                Some(token_count)
1853            }
1854            ActiveView::PromptEditor { context_editor, .. } => {
1855                let element = render_remaining_tokens(context_editor, cx)?;
1856
1857                Some(element.into_any_element())
1858            }
1859            _ => None,
1860        }
1861    }
1862
1863    fn should_render_upsell(&self, cx: &mut Context<Self>) -> bool {
1864        if !matches!(self.active_view, ActiveView::Thread { .. }) {
1865            return false;
1866        }
1867
1868        if self.hide_trial_upsell || dismissed_trial_upsell() {
1869            return false;
1870        }
1871
1872        let is_using_zed_provider = self
1873            .thread
1874            .read(cx)
1875            .thread()
1876            .read(cx)
1877            .configured_model()
1878            .map_or(false, |model| {
1879                model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
1880            });
1881        if !is_using_zed_provider {
1882            return false;
1883        }
1884
1885        let plan = self.user_store.read(cx).current_plan();
1886        if matches!(plan, Some(Plan::ZedPro | Plan::ZedProTrial)) {
1887            return false;
1888        }
1889
1890        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
1891        if has_previous_trial {
1892            return false;
1893        }
1894
1895        true
1896    }
1897
1898    fn render_trial_upsell(
1899        &self,
1900        _window: &mut Window,
1901        cx: &mut Context<Self>,
1902    ) -> Option<impl IntoElement> {
1903        if !self.should_render_upsell(cx) {
1904            return None;
1905        }
1906
1907        let checkbox = CheckboxWithLabel::new(
1908            "dont-show-again",
1909            Label::new("Don't show again").color(Color::Muted),
1910            ToggleState::Unselected,
1911            move |toggle_state, _window, cx| {
1912                let toggle_state_bool = toggle_state.selected();
1913
1914                set_trial_upsell_dismissed(toggle_state_bool, cx);
1915            },
1916        );
1917
1918        Some(
1919            div().p_2().child(
1920                v_flex()
1921                    .w_full()
1922                    .elevation_2(cx)
1923                    .rounded(px(8.))
1924                    .bg(cx.theme().colors().background.alpha(0.5))
1925                    .p(px(3.))
1926
1927                    .child(
1928                        div()
1929                            .gap_2()
1930                            .flex()
1931                            .flex_col()
1932                            .size_full()
1933                            .border_1()
1934                            .rounded(px(5.))
1935                            .border_color(cx.theme().colors().text.alpha(0.1))
1936                            .overflow_hidden()
1937                            .relative()
1938                            .bg(cx.theme().colors().panel_background)
1939                            .px_4()
1940                            .py_3()
1941                            .child(
1942                                div()
1943                                    .absolute()
1944                                    .top_0()
1945                                    .right(px(-1.0))
1946                                    .w(px(441.))
1947                                    .h(px(167.))
1948                                    .child(
1949                                    Vector::new(VectorName::Grid, rems_from_px(441.), rems_from_px(167.)).color(ui::Color::Custom(cx.theme().colors().text.alpha(0.1)))
1950                                )
1951                            )
1952                            .child(
1953                                div()
1954                                    .absolute()
1955                                    .top(px(-8.0))
1956                                    .right_0()
1957                                    .w(px(400.))
1958                                    .h(px(92.))
1959                                    .child(
1960                                    Vector::new(VectorName::AiGrid, rems_from_px(400.), rems_from_px(92.)).color(ui::Color::Custom(cx.theme().colors().text.alpha(0.32)))
1961                                )
1962                            )
1963                            // .child(
1964                            //     div()
1965                            //         .absolute()
1966                            //         .top_0()
1967                            //         .right(px(360.))
1968                            //         .size(px(401.))
1969                            //         .overflow_hidden()
1970                            //         .bg(cx.theme().colors().panel_background)
1971                            // )
1972                            .child(
1973                                div()
1974                                    .absolute()
1975                                    .top_0()
1976                                    .right_0()
1977                                    .w(px(660.))
1978                                    .h(px(401.))
1979                                    .overflow_hidden()
1980                                    .bg(linear_gradient(
1981                                        75.,
1982                                        linear_color_stop(cx.theme().colors().panel_background.alpha(0.01), 1.0),
1983                                        linear_color_stop(cx.theme().colors().panel_background, 0.45),
1984                                    ))
1985                            )
1986                            .child(Headline::new("Build better with Zed Pro").size(HeadlineSize::Small))
1987                            .child(Label::new("Try Zed Pro for free for 14 days - no credit card required.").size(LabelSize::Small))
1988                            .child(Label::new("Use your own API keys or enable usage-based billing once you hit the cap.").color(Color::Muted))
1989                            .child(
1990                                h_flex()
1991                                    .w_full()
1992                                    .px_neg_1()
1993                                    .justify_between()
1994                                    .items_center()
1995                                    .child(h_flex().items_center().gap_1().child(checkbox))
1996                                    .child(
1997                                        h_flex()
1998                                            .gap_2()
1999                                            .child(
2000                                                Button::new("dismiss-button", "Not Now")
2001                                                    .style(ButtonStyle::Transparent)
2002                                                    .color(Color::Muted)
2003                                                    .on_click({
2004                                                        let agent_panel = cx.entity();
2005                                                        move |_, _, cx| {
2006                                                            agent_panel.update(
2007                                                                cx,
2008                                                                |this, cx| {
2009                                                                    let hidden =
2010                                                                        this.hide_trial_upsell;
2011                                                                    println!("hidden: {}", hidden);
2012                                                                    this.hide_trial_upsell = true;
2013                                                                    let new_hidden =
2014                                                                        this.hide_trial_upsell;
2015                                                                    println!(
2016                                                                        "new_hidden: {}",
2017                                                                        new_hidden
2018                                                                    );
2019
2020                                                                    cx.notify();
2021                                                                },
2022                                                            );
2023                                                        }
2024                                                    }),
2025                                            )
2026                                            .child(
2027                                                Button::new("cta-button", "Start Trial")
2028                                                    .style(ButtonStyle::Transparent)
2029                                                    .on_click(|_, _, cx| {
2030                                                        cx.open_url(&zed_urls::account_url(cx))
2031                                                    }),
2032                                            ),
2033                                    ),
2034                            ),
2035                    ),
2036            ),
2037        )
2038    }
2039
2040    fn render_active_thread_or_empty_state(
2041        &self,
2042        window: &mut Window,
2043        cx: &mut Context<Self>,
2044    ) -> AnyElement {
2045        if self.thread.read(cx).is_empty() {
2046            return self
2047                .render_thread_empty_state(window, cx)
2048                .into_any_element();
2049        }
2050
2051        self.thread.clone().into_any_element()
2052    }
2053
2054    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
2055        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
2056            return Some(ConfigurationError::NoProvider);
2057        };
2058
2059        if !model.provider.is_authenticated(cx) {
2060            return Some(ConfigurationError::ProviderNotAuthenticated);
2061        }
2062
2063        if model.provider.must_accept_terms(cx) {
2064            return Some(ConfigurationError::ProviderPendingTermsAcceptance(
2065                model.provider,
2066            ));
2067        }
2068
2069        None
2070    }
2071
2072    fn render_thread_empty_state(
2073        &self,
2074        window: &mut Window,
2075        cx: &mut Context<Self>,
2076    ) -> impl IntoElement {
2077        let recent_history = self
2078            .history_store
2079            .update(cx, |this, cx| this.recent_entries(6, cx));
2080
2081        let configuration_error = self.configuration_error(cx);
2082        let no_error = configuration_error.is_none();
2083        let focus_handle = self.focus_handle(cx);
2084
2085        v_flex()
2086            .size_full()
2087            .when(recent_history.is_empty(), |this| {
2088                let configuration_error_ref = &configuration_error;
2089                this.child(
2090                    v_flex()
2091                        .size_full()
2092                        .max_w_80()
2093                        .mx_auto()
2094                        .justify_center()
2095                        .items_center()
2096                        .gap_1()
2097                        .child(
2098                            h_flex().child(
2099                                Headline::new("Welcome to the Agent Panel")
2100                            ),
2101                        )
2102                        .when(no_error, |parent| {
2103                            parent
2104                                .child(
2105                                    h_flex().child(
2106                                        Label::new("Ask and build anything.")
2107                                            .color(Color::Muted)
2108                                            .mb_2p5(),
2109                                    ),
2110                                )
2111                                .child(
2112                                    Button::new("new-thread", "Start New Thread")
2113                                        .icon(IconName::Plus)
2114                                        .icon_position(IconPosition::Start)
2115                                        .icon_size(IconSize::Small)
2116                                        .icon_color(Color::Muted)
2117                                        .full_width()
2118                                        .key_binding(KeyBinding::for_action_in(
2119                                            &NewThread::default(),
2120                                            &focus_handle,
2121                                            window,
2122                                            cx,
2123                                        ))
2124                                        .on_click(|_event, window, cx| {
2125                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
2126                                        }),
2127                                )
2128                                .child(
2129                                    Button::new("context", "Add Context")
2130                                        .icon(IconName::FileCode)
2131                                        .icon_position(IconPosition::Start)
2132                                        .icon_size(IconSize::Small)
2133                                        .icon_color(Color::Muted)
2134                                        .full_width()
2135                                        .key_binding(KeyBinding::for_action_in(
2136                                            &ToggleContextPicker,
2137                                            &focus_handle,
2138                                            window,
2139                                            cx,
2140                                        ))
2141                                        .on_click(|_event, window, cx| {
2142                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
2143                                        }),
2144                                )
2145                                .child(
2146                                    Button::new("mode", "Switch Model")
2147                                        .icon(IconName::DatabaseZap)
2148                                        .icon_position(IconPosition::Start)
2149                                        .icon_size(IconSize::Small)
2150                                        .icon_color(Color::Muted)
2151                                        .full_width()
2152                                        .key_binding(KeyBinding::for_action_in(
2153                                            &ToggleModelSelector,
2154                                            &focus_handle,
2155                                            window,
2156                                            cx,
2157                                        ))
2158                                        .on_click(|_event, window, cx| {
2159                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
2160                                        }),
2161                                )
2162                                .child(
2163                                    Button::new("settings", "View Settings")
2164                                        .icon(IconName::Settings)
2165                                        .icon_position(IconPosition::Start)
2166                                        .icon_size(IconSize::Small)
2167                                        .icon_color(Color::Muted)
2168                                        .full_width()
2169                                        .key_binding(KeyBinding::for_action_in(
2170                                            &OpenConfiguration,
2171                                            &focus_handle,
2172                                            window,
2173                                            cx,
2174                                        ))
2175                                        .on_click(|_event, window, cx| {
2176                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
2177                                        }),
2178                                )
2179                        })
2180                        .map(|parent| {
2181                            match configuration_error_ref {
2182                                Some(ConfigurationError::ProviderNotAuthenticated)
2183                                | Some(ConfigurationError::NoProvider) => {
2184                                    parent
2185                                        .child(
2186                                            h_flex().child(
2187                                                Label::new("To start using the agent, configure at least one LLM provider.")
2188                                                    .color(Color::Muted)
2189                                                    .mb_2p5()
2190                                            )
2191                                        )
2192                                        .child(
2193                                            Button::new("settings", "Configure a Provider")
2194                                                .icon(IconName::Settings)
2195                                                .icon_position(IconPosition::Start)
2196                                                .icon_size(IconSize::Small)
2197                                                .icon_color(Color::Muted)
2198                                                .full_width()
2199                                                .key_binding(KeyBinding::for_action_in(
2200                                                    &OpenConfiguration,
2201                                                    &focus_handle,
2202                                                    window,
2203                                                    cx,
2204                                                ))
2205                                                .on_click(|_event, window, cx| {
2206                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
2207                                                }),
2208                                        )
2209                                }
2210                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2211                                    parent.children(
2212                                        provider.render_accept_terms(
2213                                            LanguageModelProviderTosView::ThreadFreshStart,
2214                                            cx,
2215                                        ),
2216                                    )
2217                                }
2218                                None => parent,
2219                            }
2220                        })
2221                )
2222            })
2223            .when(!recent_history.is_empty(), |parent| {
2224                let focus_handle = focus_handle.clone();
2225                let configuration_error_ref = &configuration_error;
2226
2227                parent
2228                    .overflow_hidden()
2229                    .p_1p5()
2230                    .justify_end()
2231                    .gap_1()
2232                    .child(
2233                        h_flex()
2234                            .pl_1p5()
2235                            .pb_1()
2236                            .w_full()
2237                            .justify_between()
2238                            .border_b_1()
2239                            .border_color(cx.theme().colors().border_variant)
2240                            .child(
2241                                Label::new("Recent")
2242                                    .size(LabelSize::Small)
2243                                    .color(Color::Muted),
2244                            )
2245                            .child(
2246                                Button::new("view-history", "View All")
2247                                    .style(ButtonStyle::Subtle)
2248                                    .label_size(LabelSize::Small)
2249                                    .key_binding(
2250                                        KeyBinding::for_action_in(
2251                                            &OpenHistory,
2252                                            &self.focus_handle(cx),
2253                                            window,
2254                                            cx,
2255                                        ).map(|kb| kb.size(rems_from_px(12.))),
2256                                    )
2257                                    .on_click(move |_event, window, cx| {
2258                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
2259                                    }),
2260                            ),
2261                    )
2262                    .child(
2263                        v_flex()
2264                            .gap_1()
2265                            .children(
2266                                recent_history.into_iter().enumerate().map(|(index, entry)| {
2267                                    // TODO: Add keyboard navigation.
2268                                    let is_hovered = self.hovered_recent_history_item == Some(index);
2269                                    HistoryEntryElement::new(entry.clone(), cx.entity().downgrade())
2270                                        .hovered(is_hovered)
2271                                        .on_hover(cx.listener(move |this, is_hovered, _window, cx| {
2272                                            if *is_hovered {
2273                                                this.hovered_recent_history_item = Some(index);
2274                                            } else if this.hovered_recent_history_item == Some(index) {
2275                                                this.hovered_recent_history_item = None;
2276                                            }
2277                                            cx.notify();
2278                                        }))
2279                                        .into_any_element()
2280                                }),
2281                            )
2282                    )
2283                    .map(|parent| {
2284                        match configuration_error_ref {
2285                            Some(ConfigurationError::ProviderNotAuthenticated)
2286                            | Some(ConfigurationError::NoProvider) => {
2287                                parent
2288                                    .child(
2289                                        Banner::new()
2290                                            .severity(ui::Severity::Warning)
2291                                            .child(
2292                                                Label::new(
2293                                                    "Configure at least one LLM provider to start using the panel.",
2294                                                )
2295                                                .size(LabelSize::Small),
2296                                            )
2297                                            .action_slot(
2298                                                Button::new("settings", "Configure Provider")
2299                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
2300                                                    .label_size(LabelSize::Small)
2301                                                    .key_binding(
2302                                                        KeyBinding::for_action_in(
2303                                                            &OpenConfiguration,
2304                                                            &focus_handle,
2305                                                            window,
2306                                                            cx,
2307                                                        )
2308                                                        .map(|kb| kb.size(rems_from_px(12.))),
2309                                                    )
2310                                                    .on_click(|_event, window, cx| {
2311                                                        window.dispatch_action(
2312                                                            OpenConfiguration.boxed_clone(),
2313                                                            cx,
2314                                                        )
2315                                                    }),
2316                                            ),
2317                                    )
2318                            }
2319                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
2320                                parent
2321                                    .child(
2322                                        Banner::new()
2323                                            .severity(ui::Severity::Warning)
2324                                            .child(
2325                                                h_flex()
2326                                                    .w_full()
2327                                                    .children(
2328                                                        provider.render_accept_terms(
2329                                                            LanguageModelProviderTosView::ThreadtEmptyState,
2330                                                            cx,
2331                                                        ),
2332                                                    ),
2333                                            ),
2334                                    )
2335                            }
2336                            None => parent,
2337                        }
2338                    })
2339            })
2340    }
2341
2342    fn render_tool_use_limit_reached(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2343        let tool_use_limit_reached = self
2344            .thread
2345            .read(cx)
2346            .thread()
2347            .read(cx)
2348            .tool_use_limit_reached();
2349        if !tool_use_limit_reached {
2350            return None;
2351        }
2352
2353        let model = self
2354            .thread
2355            .read(cx)
2356            .thread()
2357            .read(cx)
2358            .configured_model()?
2359            .model;
2360
2361        let max_mode_upsell = if model.supports_max_mode() {
2362            " Enable max mode for unlimited tool use."
2363        } else {
2364            ""
2365        };
2366
2367        let banner = Banner::new()
2368            .severity(ui::Severity::Info)
2369            .child(h_flex().child(Label::new(format!(
2370                "Consecutive tool use limit reached.{max_mode_upsell}"
2371            ))));
2372
2373        Some(div().px_2().pb_2().child(banner).into_any_element())
2374    }
2375
2376    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2377        let last_error = self.thread.read(cx).last_error()?;
2378
2379        Some(
2380            div()
2381                .absolute()
2382                .right_3()
2383                .bottom_12()
2384                .max_w_96()
2385                .py_2()
2386                .px_3()
2387                .elevation_2(cx)
2388                .occlude()
2389                .child(match last_error {
2390                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
2391                    ThreadError::MaxMonthlySpendReached => {
2392                        self.render_max_monthly_spend_reached_error(cx)
2393                    }
2394                    ThreadError::ModelRequestLimitReached { plan } => {
2395                        self.render_model_request_limit_reached_error(plan, cx)
2396                    }
2397                    ThreadError::Message { header, message } => {
2398                        self.render_error_message(header, message, cx)
2399                    }
2400                })
2401                .into_any(),
2402        )
2403    }
2404
2405    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
2406        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.";
2407
2408        v_flex()
2409            .gap_0p5()
2410            .child(
2411                h_flex()
2412                    .gap_1p5()
2413                    .items_center()
2414                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2415                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
2416            )
2417            .child(
2418                div()
2419                    .id("error-message")
2420                    .max_h_24()
2421                    .overflow_y_scroll()
2422                    .child(Label::new(ERROR_MESSAGE)),
2423            )
2424            .child(
2425                h_flex()
2426                    .justify_end()
2427                    .mt_1()
2428                    .gap_1()
2429                    .child(self.create_copy_button(ERROR_MESSAGE))
2430                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
2431                        |this, _, _, cx| {
2432                            this.thread.update(cx, |this, _cx| {
2433                                this.clear_last_error();
2434                            });
2435
2436                            cx.open_url(&zed_urls::account_url(cx));
2437                            cx.notify();
2438                        },
2439                    )))
2440                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2441                        |this, _, _, cx| {
2442                            this.thread.update(cx, |this, _cx| {
2443                                this.clear_last_error();
2444                            });
2445
2446                            cx.notify();
2447                        },
2448                    ))),
2449            )
2450            .into_any()
2451    }
2452
2453    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
2454        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
2455
2456        v_flex()
2457            .gap_0p5()
2458            .child(
2459                h_flex()
2460                    .gap_1p5()
2461                    .items_center()
2462                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2463                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
2464            )
2465            .child(
2466                div()
2467                    .id("error-message")
2468                    .max_h_24()
2469                    .overflow_y_scroll()
2470                    .child(Label::new(ERROR_MESSAGE)),
2471            )
2472            .child(
2473                h_flex()
2474                    .justify_end()
2475                    .mt_1()
2476                    .gap_1()
2477                    .child(self.create_copy_button(ERROR_MESSAGE))
2478                    .child(
2479                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
2480                            cx.listener(|this, _, _, cx| {
2481                                this.thread.update(cx, |this, _cx| {
2482                                    this.clear_last_error();
2483                                });
2484
2485                                cx.open_url(&zed_urls::account_url(cx));
2486                                cx.notify();
2487                            }),
2488                        ),
2489                    )
2490                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2491                        |this, _, _, cx| {
2492                            this.thread.update(cx, |this, _cx| {
2493                                this.clear_last_error();
2494                            });
2495
2496                            cx.notify();
2497                        },
2498                    ))),
2499            )
2500            .into_any()
2501    }
2502
2503    fn render_model_request_limit_reached_error(
2504        &self,
2505        plan: Plan,
2506        cx: &mut Context<Self>,
2507    ) -> AnyElement {
2508        let error_message = match plan {
2509            Plan::ZedPro => {
2510                "Model request limit reached. Upgrade to usage-based billing for more requests."
2511            }
2512            Plan::ZedProTrial => {
2513                "Model request limit reached. Upgrade to Zed Pro for more requests."
2514            }
2515            Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
2516        };
2517        let call_to_action = match plan {
2518            Plan::ZedPro => "Upgrade to usage-based billing",
2519            Plan::ZedProTrial => "Upgrade to Zed Pro",
2520            Plan::Free => "Upgrade to Zed Pro",
2521        };
2522
2523        v_flex()
2524            .gap_0p5()
2525            .child(
2526                h_flex()
2527                    .gap_1p5()
2528                    .items_center()
2529                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2530                    .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)),
2531            )
2532            .child(
2533                div()
2534                    .id("error-message")
2535                    .max_h_24()
2536                    .overflow_y_scroll()
2537                    .child(Label::new(error_message)),
2538            )
2539            .child(
2540                h_flex()
2541                    .justify_end()
2542                    .mt_1()
2543                    .gap_1()
2544                    .child(self.create_copy_button(error_message))
2545                    .child(
2546                        Button::new("subscribe", call_to_action).on_click(cx.listener(
2547                            |this, _, _, cx| {
2548                                this.thread.update(cx, |this, _cx| {
2549                                    this.clear_last_error();
2550                                });
2551
2552                                cx.open_url(&zed_urls::account_url(cx));
2553                                cx.notify();
2554                            },
2555                        )),
2556                    )
2557                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2558                        |this, _, _, cx| {
2559                            this.thread.update(cx, |this, _cx| {
2560                                this.clear_last_error();
2561                            });
2562
2563                            cx.notify();
2564                        },
2565                    ))),
2566            )
2567            .into_any()
2568    }
2569
2570    fn render_error_message(
2571        &self,
2572        header: SharedString,
2573        message: SharedString,
2574        cx: &mut Context<Self>,
2575    ) -> AnyElement {
2576        let message_with_header = format!("{}\n{}", header, message);
2577        v_flex()
2578            .gap_0p5()
2579            .child(
2580                h_flex()
2581                    .gap_1p5()
2582                    .items_center()
2583                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2584                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
2585            )
2586            .child(
2587                div()
2588                    .id("error-message")
2589                    .max_h_32()
2590                    .overflow_y_scroll()
2591                    .child(Label::new(message.clone())),
2592            )
2593            .child(
2594                h_flex()
2595                    .justify_end()
2596                    .mt_1()
2597                    .gap_1()
2598                    .child(self.create_copy_button(message_with_header))
2599                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2600                        |this, _, _, cx| {
2601                            this.thread.update(cx, |this, _cx| {
2602                                this.clear_last_error();
2603                            });
2604
2605                            cx.notify();
2606                        },
2607                    ))),
2608            )
2609            .into_any()
2610    }
2611
2612    fn render_prompt_editor(
2613        &self,
2614        context_editor: &Entity<ContextEditor>,
2615        buffer_search_bar: &Entity<BufferSearchBar>,
2616        window: &mut Window,
2617        cx: &mut Context<Self>,
2618    ) -> Div {
2619        let mut registrar = buffer_search::DivRegistrar::new(
2620            |this, _, _cx| match &this.active_view {
2621                ActiveView::PromptEditor {
2622                    buffer_search_bar, ..
2623                } => Some(buffer_search_bar.clone()),
2624                _ => None,
2625            },
2626            cx,
2627        );
2628        BufferSearchBar::register(&mut registrar);
2629        registrar
2630            .into_div()
2631            .size_full()
2632            .relative()
2633            .map(|parent| {
2634                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
2635                    if buffer_search_bar.is_dismissed() {
2636                        return parent;
2637                    }
2638                    parent.child(
2639                        div()
2640                            .p(DynamicSpacing::Base08.rems(cx))
2641                            .border_b_1()
2642                            .border_color(cx.theme().colors().border_variant)
2643                            .bg(cx.theme().colors().editor_background)
2644                            .child(buffer_search_bar.render(window, cx)),
2645                    )
2646                })
2647            })
2648            .child(context_editor.clone())
2649            .child(self.render_drag_target(cx))
2650    }
2651
2652    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
2653        let is_local = self.project.read(cx).is_local();
2654        div()
2655            .invisible()
2656            .absolute()
2657            .top_0()
2658            .right_0()
2659            .bottom_0()
2660            .left_0()
2661            .bg(cx.theme().colors().drop_target_background)
2662            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
2663            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
2664            .when(is_local, |this| {
2665                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
2666            })
2667            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
2668                let item = tab.pane.read(cx).item_for_index(tab.ix);
2669                let project_paths = item
2670                    .and_then(|item| item.project_path(cx))
2671                    .into_iter()
2672                    .collect::<Vec<_>>();
2673                this.handle_drop(project_paths, vec![], window, cx);
2674            }))
2675            .on_drop(
2676                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
2677                    let project_paths = selection
2678                        .items()
2679                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
2680                        .collect::<Vec<_>>();
2681                    this.handle_drop(project_paths, vec![], window, cx);
2682                }),
2683            )
2684            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
2685                let tasks = paths
2686                    .paths()
2687                    .into_iter()
2688                    .map(|path| {
2689                        Workspace::project_path_for_path(this.project.clone(), &path, false, cx)
2690                    })
2691                    .collect::<Vec<_>>();
2692                cx.spawn_in(window, async move |this, cx| {
2693                    let mut paths = vec![];
2694                    let mut added_worktrees = vec![];
2695                    let opened_paths = futures::future::join_all(tasks).await;
2696                    for entry in opened_paths {
2697                        if let Some((worktree, project_path)) = entry.log_err() {
2698                            added_worktrees.push(worktree);
2699                            paths.push(project_path);
2700                        }
2701                    }
2702                    this.update_in(cx, |this, window, cx| {
2703                        this.handle_drop(paths, added_worktrees, window, cx);
2704                    })
2705                    .ok();
2706                })
2707                .detach();
2708            }))
2709    }
2710
2711    fn handle_drop(
2712        &mut self,
2713        paths: Vec<ProjectPath>,
2714        added_worktrees: Vec<Entity<Worktree>>,
2715        window: &mut Window,
2716        cx: &mut Context<Self>,
2717    ) {
2718        match &self.active_view {
2719            ActiveView::Thread { .. } => {
2720                let context_store = self.thread.read(cx).context_store().clone();
2721                context_store.update(cx, move |context_store, cx| {
2722                    let mut tasks = Vec::new();
2723                    for project_path in &paths {
2724                        tasks.push(context_store.add_file_from_path(
2725                            project_path.clone(),
2726                            false,
2727                            cx,
2728                        ));
2729                    }
2730                    cx.background_spawn(async move {
2731                        futures::future::join_all(tasks).await;
2732                        // Need to hold onto the worktrees until they have already been used when
2733                        // opening the buffers.
2734                        drop(added_worktrees);
2735                    })
2736                    .detach();
2737                });
2738            }
2739            ActiveView::PromptEditor { context_editor, .. } => {
2740                context_editor.update(cx, |context_editor, cx| {
2741                    ContextEditor::insert_dragged_files(
2742                        context_editor,
2743                        paths,
2744                        added_worktrees,
2745                        window,
2746                        cx,
2747                    );
2748                });
2749            }
2750            ActiveView::History | ActiveView::Configuration => {}
2751        }
2752    }
2753
2754    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
2755        let message = message.into();
2756        IconButton::new("copy", IconName::Copy)
2757            .on_click(move |_, _, cx| {
2758                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
2759            })
2760            .tooltip(Tooltip::text("Copy Error Message"))
2761    }
2762
2763    fn key_context(&self) -> KeyContext {
2764        let mut key_context = KeyContext::new_with_defaults();
2765        key_context.add("AgentPanel");
2766        if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
2767            key_context.add("prompt_editor");
2768        }
2769        key_context
2770    }
2771}
2772
2773impl Render for AgentPanel {
2774    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2775        // WARNING: Changes to this element hierarchy can have
2776        // non-obvious implications to the layout of children.
2777        //
2778        // If you need to change it, please confirm:
2779        // - The message editor expands (⌘esc) correctly
2780        // - When expanded, the buttons at the bottom of the panel are displayed correctly
2781        // - Font size works as expected and can be changed with ⌘+/⌘-
2782        // - Scrolling in all views works as expected
2783        // - Files can be dropped into the panel
2784        let content = v_flex()
2785            .key_context(self.key_context())
2786            .justify_between()
2787            .size_full()
2788            .on_action(cx.listener(Self::cancel))
2789            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
2790                this.new_thread(action, window, cx);
2791            }))
2792            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
2793                this.open_history(window, cx);
2794            }))
2795            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
2796                this.open_configuration(window, cx);
2797            }))
2798            .on_action(cx.listener(Self::open_active_thread_as_markdown))
2799            .on_action(cx.listener(Self::deploy_rules_library))
2800            .on_action(cx.listener(Self::open_agent_diff))
2801            .on_action(cx.listener(Self::go_back))
2802            .on_action(cx.listener(Self::toggle_navigation_menu))
2803            .on_action(cx.listener(Self::toggle_options_menu))
2804            .on_action(cx.listener(Self::increase_font_size))
2805            .on_action(cx.listener(Self::decrease_font_size))
2806            .on_action(cx.listener(Self::reset_font_size))
2807            .on_action(cx.listener(Self::toggle_zoom))
2808            .child(self.render_toolbar(window, cx))
2809            .children(self.render_trial_upsell(window, cx))
2810            .map(|parent| match &self.active_view {
2811                ActiveView::Thread { .. } => parent
2812                    .relative()
2813                    .child(self.render_active_thread_or_empty_state(window, cx))
2814                    .children(self.render_tool_use_limit_reached(cx))
2815                    .child(h_flex().child(self.message_editor.clone()))
2816                    .children(self.render_last_error(cx))
2817                    .child(self.render_drag_target(cx)),
2818                ActiveView::History => parent.child(self.history.clone()),
2819                ActiveView::PromptEditor {
2820                    context_editor,
2821                    buffer_search_bar,
2822                    ..
2823                } => parent.child(self.render_prompt_editor(
2824                    context_editor,
2825                    buffer_search_bar,
2826                    window,
2827                    cx,
2828                )),
2829                ActiveView::Configuration => parent.children(self.configuration.clone()),
2830            });
2831
2832        match self.active_view.which_font_size_used() {
2833            WhichFontSize::AgentFont => {
2834                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
2835                    .size_full()
2836                    .child(content)
2837                    .into_any()
2838            }
2839            _ => content.into_any(),
2840        }
2841    }
2842}
2843
2844struct PromptLibraryInlineAssist {
2845    workspace: WeakEntity<Workspace>,
2846}
2847
2848impl PromptLibraryInlineAssist {
2849    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
2850        Self { workspace }
2851    }
2852}
2853
2854impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
2855    fn assist(
2856        &self,
2857        prompt_editor: &Entity<Editor>,
2858        initial_prompt: Option<String>,
2859        window: &mut Window,
2860        cx: &mut Context<RulesLibrary>,
2861    ) {
2862        InlineAssistant::update_global(cx, |assistant, cx| {
2863            let Some(project) = self
2864                .workspace
2865                .upgrade()
2866                .map(|workspace| workspace.read(cx).project().downgrade())
2867            else {
2868                return;
2869            };
2870            let prompt_store = None;
2871            let thread_store = None;
2872            let text_thread_store = None;
2873            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
2874            assistant.assist(
2875                &prompt_editor,
2876                self.workspace.clone(),
2877                context_store,
2878                project,
2879                prompt_store,
2880                thread_store,
2881                text_thread_store,
2882                initial_prompt,
2883                window,
2884                cx,
2885            )
2886        })
2887    }
2888
2889    fn focus_agent_panel(
2890        &self,
2891        workspace: &mut Workspace,
2892        window: &mut Window,
2893        cx: &mut Context<Workspace>,
2894    ) -> bool {
2895        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
2896    }
2897}
2898
2899pub struct ConcreteAssistantPanelDelegate;
2900
2901impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
2902    fn active_context_editor(
2903        &self,
2904        workspace: &mut Workspace,
2905        _window: &mut Window,
2906        cx: &mut Context<Workspace>,
2907    ) -> Option<Entity<ContextEditor>> {
2908        let panel = workspace.panel::<AgentPanel>(cx)?;
2909        panel.read(cx).active_context_editor()
2910    }
2911
2912    fn open_saved_context(
2913        &self,
2914        workspace: &mut Workspace,
2915        path: Arc<Path>,
2916        window: &mut Window,
2917        cx: &mut Context<Workspace>,
2918    ) -> Task<Result<()>> {
2919        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
2920            return Task::ready(Err(anyhow!("Agent panel not found")));
2921        };
2922
2923        panel.update(cx, |panel, cx| {
2924            panel.open_saved_prompt_editor(path, window, cx)
2925        })
2926    }
2927
2928    fn open_remote_context(
2929        &self,
2930        _workspace: &mut Workspace,
2931        _context_id: assistant_context_editor::ContextId,
2932        _window: &mut Window,
2933        _cx: &mut Context<Workspace>,
2934    ) -> Task<Result<Entity<ContextEditor>>> {
2935        Task::ready(Err(anyhow!("opening remote context not implemented")))
2936    }
2937
2938    fn quote_selection(
2939        &self,
2940        workspace: &mut Workspace,
2941        selection_ranges: Vec<Range<Anchor>>,
2942        buffer: Entity<MultiBuffer>,
2943        window: &mut Window,
2944        cx: &mut Context<Workspace>,
2945    ) {
2946        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
2947            return;
2948        };
2949
2950        if !panel.focus_handle(cx).contains_focused(window, cx) {
2951            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
2952        }
2953
2954        panel.update(cx, |_, cx| {
2955            // Wait to create a new context until the workspace is no longer
2956            // being updated.
2957            cx.defer_in(window, move |panel, window, cx| {
2958                if panel.has_active_thread() {
2959                    panel.message_editor.update(cx, |message_editor, cx| {
2960                        message_editor.context_store().update(cx, |store, cx| {
2961                            let buffer = buffer.read(cx);
2962                            let selection_ranges = selection_ranges
2963                                .into_iter()
2964                                .flat_map(|range| {
2965                                    let (start_buffer, start) =
2966                                        buffer.text_anchor_for_position(range.start, cx)?;
2967                                    let (end_buffer, end) =
2968                                        buffer.text_anchor_for_position(range.end, cx)?;
2969                                    if start_buffer != end_buffer {
2970                                        return None;
2971                                    }
2972                                    Some((start_buffer, start..end))
2973                                })
2974                                .collect::<Vec<_>>();
2975
2976                            for (buffer, range) in selection_ranges {
2977                                store.add_selection(buffer, range, cx);
2978                            }
2979                        })
2980                    })
2981                } else if let Some(context_editor) = panel.active_context_editor() {
2982                    let snapshot = buffer.read(cx).snapshot(cx);
2983                    let selection_ranges = selection_ranges
2984                        .into_iter()
2985                        .map(|range| range.to_point(&snapshot))
2986                        .collect::<Vec<_>>();
2987
2988                    context_editor.update(cx, |context_editor, cx| {
2989                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
2990                    });
2991                }
2992            });
2993        });
2994    }
2995}
2996
2997const DISMISSED_TRIAL_UPSELL_KEY: &str = "dismissed-trial-upsell";
2998
2999fn dismissed_trial_upsell() -> bool {
3000    db::kvp::KEY_VALUE_STORE
3001        .read_kvp(DISMISSED_TRIAL_UPSELL_KEY)
3002        .log_err()
3003        .map_or(false, |s| s.is_some())
3004}
3005
3006fn set_trial_upsell_dismissed(is_dismissed: bool, cx: &mut App) {
3007    db::write_and_log(cx, move || async move {
3008        if is_dismissed {
3009            db::kvp::KEY_VALUE_STORE
3010                .write_kvp(DISMISSED_TRIAL_UPSELL_KEY.into(), "1".into())
3011                .await
3012        } else {
3013            db::kvp::KEY_VALUE_STORE
3014                .delete_kvp(DISMISSED_TRIAL_UPSELL_KEY.into())
3015                .await
3016        }
3017    })
3018}