assistant_panel.rs

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