agent_panel.rs

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