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