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