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