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