assistant_panel.rs

   1use crate::context_editor::{
   2    ContextEditor, ContextEditorToolbarItem, ContextEditorToolbarItemEvent, DEFAULT_TAB_TITLE,
   3};
   4use crate::context_history::ContextHistory;
   5use crate::{
   6    slash_command::SlashCommandCompletionProvider,
   7    terminal_inline_assistant::TerminalInlineAssistant, Context, ContextId, ContextStore,
   8    ContextStoreEvent, DeployHistory, DeployPromptLibrary, InlineAssistant, InsertDraggedFiles,
   9    NewContext, ToggleFocus, ToggleModelSelector,
  10};
  11use anyhow::Result;
  12use assistant_settings::{AssistantDockPosition, AssistantSettings};
  13use assistant_slash_command::SlashCommandWorkingSet;
  14use assistant_tool::ToolWorkingSet;
  15use client::{proto, Client, Status};
  16use collections::HashMap;
  17use editor::{Editor, EditorEvent};
  18use fs::Fs;
  19use gpui::{
  20    canvas, div, prelude::*, Action, AnyView, AppContext, AsyncWindowContext, EventEmitter,
  21    ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model,
  22    ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
  23    Task, UpdateGlobal, View, WeakView,
  24};
  25use language::{LanguageRegistry, LspAdapterDelegate};
  26use language_model::{
  27    LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
  28};
  29use language_model_selector::LanguageModelSelector;
  30use project::lsp_store::LocalLspAdapterDelegate;
  31use project::Project;
  32use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
  33use search::{buffer_search::DivRegistrar, BufferSearchBar};
  34use settings::{update_settings_file, Settings};
  35use smol::stream::StreamExt;
  36use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
  37use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  38use ui::{prelude::*, ContextMenu, ElevationIndex, PopoverMenu, PopoverMenuHandle, Tooltip};
  39use util::{maybe, ResultExt};
  40use workspace::DraggedTab;
  41use workspace::{
  42    dock::{DockPosition, Panel, PanelEvent},
  43    item::Item,
  44    pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
  45};
  46use zed_actions::InlineAssist;
  47
  48pub fn init(cx: &mut AppContext) {
  49    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  50    cx.observe_new_views(
  51        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  52            workspace
  53                .register_action(|workspace, _: &ToggleFocus, cx| {
  54                    let settings = AssistantSettings::get_global(cx);
  55                    if !settings.enabled {
  56                        return;
  57                    }
  58
  59                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  60                })
  61                .register_action(ContextEditor::quote_selection)
  62                .register_action(ContextEditor::insert_selection)
  63                .register_action(ContextEditor::copy_code)
  64                .register_action(ContextEditor::insert_dragged_files)
  65                .register_action(AssistantPanel::show_configuration)
  66                .register_action(AssistantPanel::create_new_context)
  67                .register_action(AssistantPanel::restart_context_servers);
  68        },
  69    )
  70    .detach();
  71
  72    cx.observe_new_views(
  73        |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
  74            let settings = AssistantSettings::get_global(cx);
  75            terminal_panel.set_assistant_enabled(settings.enabled, cx);
  76        },
  77    )
  78    .detach();
  79}
  80
  81pub enum AssistantPanelEvent {
  82    ContextEdited,
  83}
  84
  85pub struct AssistantPanel {
  86    pane: View<Pane>,
  87    workspace: WeakView<Workspace>,
  88    width: Option<Pixels>,
  89    height: Option<Pixels>,
  90    project: Model<Project>,
  91    context_store: Model<ContextStore>,
  92    languages: Arc<LanguageRegistry>,
  93    fs: Arc<dyn Fs>,
  94    subscriptions: Vec<Subscription>,
  95    model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
  96    model_summary_editor: View<Editor>,
  97    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
  98    configuration_subscription: Option<Subscription>,
  99    client_status: Option<client::Status>,
 100    watch_client_status: Option<Task<()>>,
 101    pub(crate) show_zed_ai_notice: bool,
 102}
 103
 104enum InlineAssistTarget {
 105    Editor(View<Editor>, bool),
 106    Terminal(View<TerminalView>),
 107}
 108
 109impl AssistantPanel {
 110    pub fn load(
 111        workspace: WeakView<Workspace>,
 112        prompt_builder: Arc<PromptBuilder>,
 113        cx: AsyncWindowContext,
 114    ) -> Task<Result<View<Self>>> {
 115        cx.spawn(|mut cx| async move {
 116            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 117            let tools = Arc::new(ToolWorkingSet::default());
 118            let context_store = workspace
 119                .update(&mut cx, |workspace, cx| {
 120                    let project = workspace.project().clone();
 121                    ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx)
 122                })?
 123                .await?;
 124
 125            workspace.update(&mut cx, |workspace, cx| {
 126                // TODO: deserialize state.
 127                cx.new_view(|cx| Self::new(workspace, context_store, cx))
 128            })
 129        })
 130    }
 131
 132    fn new(
 133        workspace: &Workspace,
 134        context_store: Model<ContextStore>,
 135        cx: &mut ViewContext<Self>,
 136    ) -> Self {
 137        let model_selector_menu_handle = PopoverMenuHandle::default();
 138        let model_summary_editor = cx.new_view(Editor::single_line);
 139        let context_editor_toolbar = cx.new_view(|cx| {
 140            ContextEditorToolbarItem::new(
 141                workspace,
 142                model_selector_menu_handle.clone(),
 143                model_summary_editor.clone(),
 144                cx,
 145            )
 146        });
 147
 148        let pane = cx.new_view(|cx| {
 149            let mut pane = Pane::new(
 150                workspace.weak_handle(),
 151                workspace.project().clone(),
 152                Default::default(),
 153                None,
 154                NewContext.boxed_clone(),
 155                cx,
 156            );
 157
 158            let project = workspace.project().clone();
 159            pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
 160                let action = maybe!({
 161                    if project.read(cx).is_local() {
 162                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
 163                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
 164                        }
 165                    }
 166
 167                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
 168                    {
 169                        if &tab.pane == cx.view() {
 170                            return None;
 171                        }
 172                        let item = tab.pane.read(cx).item_for_index(tab.ix);
 173                        Some(
 174                            item.and_then(|item| item.project_path(cx))
 175                                .into_iter()
 176                                .collect::<Vec<_>>(),
 177                        )
 178                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
 179                    {
 180                        Some(
 181                            selection
 182                                .items()
 183                                .filter_map(|item| {
 184                                    project.read(cx).path_for_entry(item.entry_id, cx)
 185                                })
 186                                .collect::<Vec<_>>(),
 187                        )
 188                    } else {
 189                        None
 190                    }?;
 191
 192                    let paths = project_paths
 193                        .into_iter()
 194                        .filter_map(|project_path| {
 195                            let worktree = project
 196                                .read(cx)
 197                                .worktree_for_id(project_path.worktree_id, cx)?;
 198
 199                            let mut full_path = PathBuf::from(worktree.read(cx).root_name());
 200                            full_path.push(&project_path.path);
 201                            Some(full_path)
 202                        })
 203                        .collect::<Vec<_>>();
 204
 205                    Some(InsertDraggedFiles::ProjectPaths(paths))
 206                });
 207
 208                if let Some(action) = action {
 209                    cx.dispatch_action(action.boxed_clone());
 210                }
 211
 212                ControlFlow::Break(())
 213            });
 214
 215            pane.set_can_navigate(true, cx);
 216            pane.display_nav_history_buttons(None);
 217            pane.set_should_display_tab_bar(|_| true);
 218            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 219                let focus_handle = pane.focus_handle(cx);
 220                let left_children = IconButton::new("history", IconName::HistoryRerun)
 221                    .icon_size(IconSize::Small)
 222                    .on_click(cx.listener({
 223                        let focus_handle = focus_handle.clone();
 224                        move |_, _, cx| {
 225                            focus_handle.focus(cx);
 226                            cx.dispatch_action(DeployHistory.boxed_clone())
 227                        }
 228                    }))
 229                    .tooltip({
 230                        let focus_handle = focus_handle.clone();
 231                        move |cx| {
 232                            Tooltip::for_action_in(
 233                                "Open History",
 234                                &DeployHistory,
 235                                &focus_handle,
 236                                cx,
 237                            )
 238                        }
 239                    })
 240                    .toggle_state(
 241                        pane.active_item()
 242                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 243                    );
 244                let _pane = cx.view().clone();
 245                let right_children = h_flex()
 246                    .gap(DynamicSpacing::Base02.rems(cx))
 247                    .child(
 248                        IconButton::new("new-chat", IconName::Plus)
 249                            .icon_size(IconSize::Small)
 250                            .on_click(
 251                                cx.listener(|_, _, cx| {
 252                                    cx.dispatch_action(NewContext.boxed_clone())
 253                                }),
 254                            )
 255                            .tooltip(move |cx| {
 256                                Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx)
 257                            }),
 258                    )
 259                    .child(
 260                        PopoverMenu::new("assistant-panel-popover-menu")
 261                            .trigger(
 262                                IconButton::new("menu", IconName::EllipsisVertical)
 263                                    .icon_size(IconSize::Small)
 264                                    .tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)),
 265                            )
 266                            .menu(move |cx| {
 267                                let zoom_label = if _pane.read(cx).is_zoomed() {
 268                                    "Zoom Out"
 269                                } else {
 270                                    "Zoom In"
 271                                };
 272                                let focus_handle = _pane.focus_handle(cx);
 273                                Some(ContextMenu::build(cx, move |menu, _| {
 274                                    menu.context(focus_handle.clone())
 275                                        .action("New Chat", Box::new(NewContext))
 276                                        .action("History", Box::new(DeployHistory))
 277                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 278                                        .action("Configure", Box::new(ShowConfiguration))
 279                                        .action(zoom_label, Box::new(ToggleZoom))
 280                                }))
 281                            }),
 282                    )
 283                    .into_any_element()
 284                    .into();
 285
 286                (Some(left_children.into_any_element()), right_children)
 287            });
 288            pane.toolbar().update(cx, |toolbar, cx| {
 289                toolbar.add_item(context_editor_toolbar.clone(), cx);
 290                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 291            });
 292            pane
 293        });
 294
 295        let subscriptions = vec![
 296            cx.observe(&pane, |_, _, cx| cx.notify()),
 297            cx.subscribe(&pane, Self::handle_pane_event),
 298            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 299            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 300            cx.subscribe(&context_store, Self::handle_context_store_event),
 301            cx.subscribe(
 302                &LanguageModelRegistry::global(cx),
 303                |this, _, event: &language_model::Event, cx| match event {
 304                    language_model::Event::ActiveModelChanged => {
 305                        this.completion_provider_changed(cx);
 306                    }
 307                    language_model::Event::ProviderStateChanged => {
 308                        this.ensure_authenticated(cx);
 309                        cx.notify()
 310                    }
 311                    language_model::Event::AddedProvider(_)
 312                    | language_model::Event::RemovedProvider(_) => {
 313                        this.ensure_authenticated(cx);
 314                    }
 315                },
 316            ),
 317        ];
 318
 319        let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
 320
 321        let mut this = Self {
 322            pane,
 323            workspace: workspace.weak_handle(),
 324            width: None,
 325            height: None,
 326            project: workspace.project().clone(),
 327            context_store,
 328            languages: workspace.app_state().languages.clone(),
 329            fs: workspace.app_state().fs.clone(),
 330            subscriptions,
 331            model_selector_menu_handle,
 332            model_summary_editor,
 333            authenticate_provider_task: None,
 334            configuration_subscription: None,
 335            client_status: None,
 336            watch_client_status: Some(watch_client_status),
 337            show_zed_ai_notice: false,
 338        };
 339        this.new_context(cx);
 340        this
 341    }
 342
 343    fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
 344        let mut status_rx = client.status();
 345
 346        cx.spawn(|this, mut cx| async move {
 347            while let Some(status) = status_rx.next().await {
 348                this.update(&mut cx, |this, cx| {
 349                    if this.client_status.is_none()
 350                        || this
 351                            .client_status
 352                            .map_or(false, |old_status| old_status != status)
 353                    {
 354                        this.update_zed_ai_notice_visibility(status, cx);
 355                    }
 356                    this.client_status = Some(status);
 357                })
 358                .log_err();
 359            }
 360            this.update(&mut cx, |this, _cx| this.watch_client_status = None)
 361                .log_err();
 362        })
 363    }
 364
 365    fn handle_pane_event(
 366        &mut self,
 367        pane: View<Pane>,
 368        event: &pane::Event,
 369        cx: &mut ViewContext<Self>,
 370    ) {
 371        let update_model_summary = match event {
 372            pane::Event::Remove { .. } => {
 373                cx.emit(PanelEvent::Close);
 374                false
 375            }
 376            pane::Event::ZoomIn => {
 377                cx.emit(PanelEvent::ZoomIn);
 378                false
 379            }
 380            pane::Event::ZoomOut => {
 381                cx.emit(PanelEvent::ZoomOut);
 382                false
 383            }
 384
 385            pane::Event::AddItem { item } => {
 386                self.workspace
 387                    .update(cx, |workspace, cx| {
 388                        item.added_to_pane(workspace, self.pane.clone(), cx)
 389                    })
 390                    .ok();
 391                true
 392            }
 393
 394            pane::Event::ActivateItem { local, .. } => {
 395                if *local {
 396                    self.workspace
 397                        .update(cx, |workspace, cx| {
 398                            workspace.unfollow_in_pane(&pane, cx);
 399                        })
 400                        .ok();
 401                }
 402                cx.emit(AssistantPanelEvent::ContextEdited);
 403                true
 404            }
 405            pane::Event::RemovedItem { .. } => {
 406                let has_configuration_view = self
 407                    .pane
 408                    .read(cx)
 409                    .items_of_type::<ConfigurationView>()
 410                    .next()
 411                    .is_some();
 412
 413                if !has_configuration_view {
 414                    self.configuration_subscription = None;
 415                }
 416
 417                cx.emit(AssistantPanelEvent::ContextEdited);
 418                true
 419            }
 420
 421            _ => false,
 422        };
 423
 424        if update_model_summary {
 425            if let Some(editor) = self.active_context_editor(cx) {
 426                self.show_updated_summary(&editor, cx)
 427            }
 428        }
 429    }
 430
 431    fn handle_summary_editor_event(
 432        &mut self,
 433        model_summary_editor: View<Editor>,
 434        event: &EditorEvent,
 435        cx: &mut ViewContext<Self>,
 436    ) {
 437        if matches!(event, EditorEvent::Edited { .. }) {
 438            if let Some(context_editor) = self.active_context_editor(cx) {
 439                let new_summary = model_summary_editor.read(cx).text(cx);
 440                context_editor.update(cx, |context_editor, cx| {
 441                    context_editor.context.update(cx, |context, cx| {
 442                        if context.summary().is_none()
 443                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 444                        {
 445                            return;
 446                        }
 447                        context.custom_summary(new_summary, cx)
 448                    });
 449                });
 450            }
 451        }
 452    }
 453
 454    fn update_zed_ai_notice_visibility(
 455        &mut self,
 456        client_status: Status,
 457        cx: &mut ViewContext<Self>,
 458    ) {
 459        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
 460
 461        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 462        // the provider, we want to show a nudge to sign in.
 463        let show_zed_ai_notice = client_status.is_signed_out()
 464            && active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);
 465
 466        self.show_zed_ai_notice = show_zed_ai_notice;
 467        cx.notify();
 468    }
 469
 470    fn handle_toolbar_event(
 471        &mut self,
 472        _: View<ContextEditorToolbarItem>,
 473        _: &ContextEditorToolbarItemEvent,
 474        cx: &mut ViewContext<Self>,
 475    ) {
 476        if let Some(context_editor) = self.active_context_editor(cx) {
 477            context_editor.update(cx, |context_editor, cx| {
 478                context_editor.context.update(cx, |context, cx| {
 479                    context.summarize(true, cx);
 480                })
 481            })
 482        }
 483    }
 484
 485    fn handle_context_store_event(
 486        &mut self,
 487        _context_store: Model<ContextStore>,
 488        event: &ContextStoreEvent,
 489        cx: &mut ViewContext<Self>,
 490    ) {
 491        let ContextStoreEvent::ContextCreated(context_id) = event;
 492        let Some(context) = self
 493            .context_store
 494            .read(cx)
 495            .loaded_context_for_id(&context_id, cx)
 496        else {
 497            log::error!("no context found with ID: {}", context_id.to_proto());
 498            return;
 499        };
 500        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 501            .log_err()
 502            .flatten();
 503
 504        let assistant_panel = cx.view().downgrade();
 505        let editor = cx.new_view(|cx| {
 506            let mut editor = ContextEditor::for_context(
 507                context,
 508                self.fs.clone(),
 509                self.workspace.clone(),
 510                self.project.clone(),
 511                lsp_adapter_delegate,
 512                assistant_panel,
 513                cx,
 514            );
 515            editor.insert_default_prompt(cx);
 516            editor
 517        });
 518
 519        self.show_context(editor.clone(), cx);
 520    }
 521
 522    fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
 523        if let Some(editor) = self.active_context_editor(cx) {
 524            editor.update(cx, |active_context, cx| {
 525                active_context
 526                    .context
 527                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 528            })
 529        }
 530
 531        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 532            .active_provider()
 533            .map(|p| p.id())
 534        else {
 535            return;
 536        };
 537
 538        if self
 539            .authenticate_provider_task
 540            .as_ref()
 541            .map_or(true, |(old_provider_id, _)| {
 542                *old_provider_id != new_provider_id
 543            })
 544        {
 545            self.authenticate_provider_task = None;
 546            self.ensure_authenticated(cx);
 547        }
 548
 549        if let Some(status) = self.client_status {
 550            self.update_zed_ai_notice_visibility(status, cx);
 551        }
 552    }
 553
 554    fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
 555        if self.is_authenticated(cx) {
 556            return;
 557        }
 558
 559        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 560            return;
 561        };
 562
 563        let load_credentials = self.authenticate(cx);
 564
 565        if self.authenticate_provider_task.is_none() {
 566            self.authenticate_provider_task = Some((
 567                provider.id(),
 568                cx.spawn(|this, mut cx| async move {
 569                    if let Some(future) = load_credentials {
 570                        let _ = future.await;
 571                    }
 572                    this.update(&mut cx, |this, _cx| {
 573                        this.authenticate_provider_task = None;
 574                    })
 575                    .log_err();
 576                }),
 577            ));
 578        }
 579    }
 580
 581    pub fn inline_assist(
 582        workspace: &mut Workspace,
 583        action: &InlineAssist,
 584        cx: &mut ViewContext<Workspace>,
 585    ) {
 586        let settings = AssistantSettings::get_global(cx);
 587        if !settings.enabled {
 588            return;
 589        }
 590
 591        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 592            return;
 593        };
 594
 595        let Some(inline_assist_target) =
 596            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 597        else {
 598            return;
 599        };
 600
 601        let initial_prompt = action.prompt.clone();
 602
 603        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 604            match inline_assist_target {
 605                InlineAssistTarget::Editor(active_editor, include_context) => {
 606                    InlineAssistant::update_global(cx, |assistant, cx| {
 607                        assistant.assist(
 608                            &active_editor,
 609                            Some(cx.view().downgrade()),
 610                            include_context.then_some(&assistant_panel),
 611                            initial_prompt,
 612                            cx,
 613                        )
 614                    })
 615                }
 616                InlineAssistTarget::Terminal(active_terminal) => {
 617                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 618                        assistant.assist(
 619                            &active_terminal,
 620                            Some(cx.view().downgrade()),
 621                            Some(&assistant_panel),
 622                            initial_prompt,
 623                            cx,
 624                        )
 625                    })
 626                }
 627            }
 628        } else {
 629            let assistant_panel = assistant_panel.downgrade();
 630            cx.spawn(|workspace, mut cx| async move {
 631                let Some(task) =
 632                    assistant_panel.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 633                else {
 634                    let answer = cx
 635                        .prompt(
 636                            gpui::PromptLevel::Warning,
 637                            "No language model provider configured",
 638                            None,
 639                            &["Configure", "Cancel"],
 640                        )
 641                        .await
 642                        .ok();
 643                    if let Some(answer) = answer {
 644                        if answer == 0 {
 645                            cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration)))
 646                                .ok();
 647                        }
 648                    }
 649                    return Ok(());
 650                };
 651                task.await?;
 652                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 653                    cx.update(|cx| match inline_assist_target {
 654                        InlineAssistTarget::Editor(active_editor, include_context) => {
 655                            let assistant_panel = if include_context {
 656                                assistant_panel.upgrade()
 657                            } else {
 658                                None
 659                            };
 660                            InlineAssistant::update_global(cx, |assistant, cx| {
 661                                assistant.assist(
 662                                    &active_editor,
 663                                    Some(workspace),
 664                                    assistant_panel.as_ref(),
 665                                    initial_prompt,
 666                                    cx,
 667                                )
 668                            })
 669                        }
 670                        InlineAssistTarget::Terminal(active_terminal) => {
 671                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 672                                assistant.assist(
 673                                    &active_terminal,
 674                                    Some(workspace),
 675                                    assistant_panel.upgrade().as_ref(),
 676                                    initial_prompt,
 677                                    cx,
 678                                )
 679                            })
 680                        }
 681                    })?
 682                } else {
 683                    workspace.update(&mut cx, |workspace, cx| {
 684                        workspace.focus_panel::<AssistantPanel>(cx)
 685                    })?;
 686                }
 687
 688                anyhow::Ok(())
 689            })
 690            .detach_and_log_err(cx)
 691        }
 692    }
 693
 694    fn resolve_inline_assist_target(
 695        workspace: &mut Workspace,
 696        assistant_panel: &View<AssistantPanel>,
 697        cx: &mut WindowContext,
 698    ) -> Option<InlineAssistTarget> {
 699        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 700            if terminal_panel
 701                .read(cx)
 702                .focus_handle(cx)
 703                .contains_focused(cx)
 704            {
 705                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 706                    pane.read(cx)
 707                        .active_item()
 708                        .and_then(|t| t.downcast::<TerminalView>())
 709                }) {
 710                    return Some(InlineAssistTarget::Terminal(terminal_view));
 711                }
 712            }
 713        }
 714        let context_editor =
 715            assistant_panel
 716                .read(cx)
 717                .active_context_editor(cx)
 718                .and_then(|editor| {
 719                    let editor = &editor.read(cx).editor;
 720                    if editor.read(cx).is_focused(cx) {
 721                        Some(editor.clone())
 722                    } else {
 723                        None
 724                    }
 725                });
 726
 727        if let Some(context_editor) = context_editor {
 728            Some(InlineAssistTarget::Editor(context_editor, false))
 729        } else if let Some(workspace_editor) = workspace
 730            .active_item(cx)
 731            .and_then(|item| item.act_as::<Editor>(cx))
 732        {
 733            Some(InlineAssistTarget::Editor(workspace_editor, true))
 734        } else if let Some(terminal_view) = workspace
 735            .active_item(cx)
 736            .and_then(|item| item.act_as::<TerminalView>(cx))
 737        {
 738            Some(InlineAssistTarget::Terminal(terminal_view))
 739        } else {
 740            None
 741        }
 742    }
 743
 744    pub fn create_new_context(
 745        workspace: &mut Workspace,
 746        _: &NewContext,
 747        cx: &mut ViewContext<Workspace>,
 748    ) {
 749        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 750            let did_create_context = panel
 751                .update(cx, |panel, cx| {
 752                    panel.new_context(cx)?;
 753
 754                    Some(())
 755                })
 756                .is_some();
 757            if did_create_context {
 758                ContextEditor::quote_selection(workspace, &Default::default(), cx);
 759            }
 760        }
 761    }
 762
 763    pub fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 764        let project = self.project.read(cx);
 765        if project.is_via_collab() {
 766            let task = self
 767                .context_store
 768                .update(cx, |store, cx| store.create_remote_context(cx));
 769
 770            cx.spawn(|this, mut cx| async move {
 771                let context = task.await?;
 772
 773                this.update(&mut cx, |this, cx| {
 774                    let workspace = this.workspace.clone();
 775                    let project = this.project.clone();
 776                    let lsp_adapter_delegate =
 777                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 778
 779                    let fs = this.fs.clone();
 780                    let project = this.project.clone();
 781                    let weak_assistant_panel = cx.view().downgrade();
 782
 783                    let editor = cx.new_view(|cx| {
 784                        ContextEditor::for_context(
 785                            context,
 786                            fs,
 787                            workspace,
 788                            project,
 789                            lsp_adapter_delegate,
 790                            weak_assistant_panel,
 791                            cx,
 792                        )
 793                    });
 794
 795                    this.show_context(editor, cx);
 796
 797                    anyhow::Ok(())
 798                })??;
 799
 800                anyhow::Ok(())
 801            })
 802            .detach_and_log_err(cx);
 803
 804            None
 805        } else {
 806            let context = self.context_store.update(cx, |store, cx| store.create(cx));
 807            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 808                .log_err()
 809                .flatten();
 810
 811            let assistant_panel = cx.view().downgrade();
 812            let editor = cx.new_view(|cx| {
 813                let mut editor = ContextEditor::for_context(
 814                    context,
 815                    self.fs.clone(),
 816                    self.workspace.clone(),
 817                    self.project.clone(),
 818                    lsp_adapter_delegate,
 819                    assistant_panel,
 820                    cx,
 821                );
 822                editor.insert_default_prompt(cx);
 823                editor
 824            });
 825
 826            self.show_context(editor.clone(), cx);
 827            let workspace = self.workspace.clone();
 828            cx.spawn(move |_, mut cx| async move {
 829                workspace
 830                    .update(&mut cx, |workspace, cx| {
 831                        workspace.focus_panel::<AssistantPanel>(cx);
 832                    })
 833                    .ok();
 834            })
 835            .detach();
 836            Some(editor)
 837        }
 838    }
 839
 840    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
 841        let focus = self.focus_handle(cx).contains_focused(cx);
 842        let prev_len = self.pane.read(cx).items_len();
 843        self.pane.update(cx, |pane, cx| {
 844            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
 845        });
 846
 847        if prev_len != self.pane.read(cx).items_len() {
 848            self.subscriptions
 849                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
 850        }
 851
 852        self.show_updated_summary(&context_editor, cx);
 853
 854        cx.emit(AssistantPanelEvent::ContextEdited);
 855        cx.notify();
 856    }
 857
 858    fn show_updated_summary(
 859        &self,
 860        context_editor: &View<ContextEditor>,
 861        cx: &mut ViewContext<Self>,
 862    ) {
 863        context_editor.update(cx, |context_editor, cx| {
 864            let new_summary = context_editor.title(cx).to_string();
 865            self.model_summary_editor.update(cx, |summary_editor, cx| {
 866                if summary_editor.text(cx) != new_summary {
 867                    summary_editor.set_text(new_summary, cx);
 868                }
 869            });
 870        });
 871    }
 872
 873    fn handle_context_editor_event(
 874        &mut self,
 875        context_editor: View<ContextEditor>,
 876        event: &EditorEvent,
 877        cx: &mut ViewContext<Self>,
 878    ) {
 879        match event {
 880            EditorEvent::TitleChanged => {
 881                self.show_updated_summary(&context_editor, cx);
 882                cx.notify()
 883            }
 884            EditorEvent::Edited { .. } => {
 885                self.workspace
 886                    .update(cx, |workspace, cx| {
 887                        let is_via_ssh = workspace
 888                            .project()
 889                            .update(cx, |project, _| project.is_via_ssh());
 890
 891                        workspace
 892                            .client()
 893                            .telemetry()
 894                            .log_edit_event("assistant panel", is_via_ssh);
 895                    })
 896                    .log_err();
 897                cx.emit(AssistantPanelEvent::ContextEdited)
 898            }
 899            _ => {}
 900        }
 901    }
 902
 903    fn show_configuration(
 904        workspace: &mut Workspace,
 905        _: &ShowConfiguration,
 906        cx: &mut ViewContext<Workspace>,
 907    ) {
 908        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
 909            return;
 910        };
 911
 912        if !panel.focus_handle(cx).contains_focused(cx) {
 913            workspace.toggle_panel_focus::<AssistantPanel>(cx);
 914        }
 915
 916        panel.update(cx, |this, cx| {
 917            this.show_configuration_tab(cx);
 918        })
 919    }
 920
 921    fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
 922        let configuration_item_ix = self
 923            .pane
 924            .read(cx)
 925            .items()
 926            .position(|item| item.downcast::<ConfigurationView>().is_some());
 927
 928        if let Some(configuration_item_ix) = configuration_item_ix {
 929            self.pane.update(cx, |pane, cx| {
 930                pane.activate_item(configuration_item_ix, true, true, cx);
 931            });
 932        } else {
 933            let configuration = cx.new_view(ConfigurationView::new);
 934            self.configuration_subscription = Some(cx.subscribe(
 935                &configuration,
 936                |this, _, event: &ConfigurationViewEvent, cx| match event {
 937                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
 938                        if LanguageModelRegistry::read_global(cx)
 939                            .active_provider()
 940                            .map_or(true, |p| p.id() != provider.id())
 941                        {
 942                            if let Some(model) = provider.provided_models(cx).first().cloned() {
 943                                update_settings_file::<AssistantSettings>(
 944                                    this.fs.clone(),
 945                                    cx,
 946                                    move |settings, _| settings.set_model(model),
 947                                );
 948                            }
 949                        }
 950
 951                        this.new_context(cx);
 952                    }
 953                },
 954            ));
 955            self.pane.update(cx, |pane, cx| {
 956                pane.add_item(Box::new(configuration), true, true, None, cx);
 957            });
 958        }
 959    }
 960
 961    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
 962        let history_item_ix = self
 963            .pane
 964            .read(cx)
 965            .items()
 966            .position(|item| item.downcast::<ContextHistory>().is_some());
 967
 968        if let Some(history_item_ix) = history_item_ix {
 969            self.pane.update(cx, |pane, cx| {
 970                pane.activate_item(history_item_ix, true, true, cx);
 971            });
 972        } else {
 973            let assistant_panel = cx.view().downgrade();
 974            let history = cx.new_view(|cx| {
 975                ContextHistory::new(
 976                    self.project.clone(),
 977                    self.context_store.clone(),
 978                    assistant_panel,
 979                    cx,
 980                )
 981            });
 982            self.pane.update(cx, |pane, cx| {
 983                pane.add_item(Box::new(history), true, true, None, cx);
 984            });
 985        }
 986    }
 987
 988    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
 989        open_prompt_library(
 990            self.languages.clone(),
 991            Box::new(PromptLibraryInlineAssist),
 992            Arc::new(|| {
 993                Box::new(SlashCommandCompletionProvider::new(
 994                    Arc::new(SlashCommandWorkingSet::default()),
 995                    None,
 996                    None,
 997                ))
 998            }),
 999            cx,
1000        )
1001        .detach_and_log_err(cx);
1002    }
1003
1004    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1005        self.model_selector_menu_handle.toggle(cx);
1006    }
1007
1008    pub(crate) fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1009        self.pane
1010            .read(cx)
1011            .active_item()?
1012            .downcast::<ContextEditor>()
1013    }
1014
1015    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1016        Some(self.active_context_editor(cx)?.read(cx).context.clone())
1017    }
1018
1019    pub fn open_saved_context(
1020        &mut self,
1021        path: PathBuf,
1022        cx: &mut ViewContext<Self>,
1023    ) -> Task<Result<()>> {
1024        let existing_context = self.pane.read(cx).items().find_map(|item| {
1025            item.downcast::<ContextEditor>()
1026                .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1027        });
1028        if let Some(existing_context) = existing_context {
1029            return cx.spawn(|this, mut cx| async move {
1030                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1031            });
1032        }
1033
1034        let context = self
1035            .context_store
1036            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1037        let fs = self.fs.clone();
1038        let project = self.project.clone();
1039        let workspace = self.workspace.clone();
1040
1041        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1042
1043        cx.spawn(|this, mut cx| async move {
1044            let context = context.await?;
1045            let assistant_panel = this.clone();
1046            this.update(&mut cx, |this, cx| {
1047                let editor = cx.new_view(|cx| {
1048                    ContextEditor::for_context(
1049                        context,
1050                        fs,
1051                        workspace,
1052                        project,
1053                        lsp_adapter_delegate,
1054                        assistant_panel,
1055                        cx,
1056                    )
1057                });
1058                this.show_context(editor, cx);
1059                anyhow::Ok(())
1060            })??;
1061            Ok(())
1062        })
1063    }
1064
1065    pub fn open_remote_context(
1066        &mut self,
1067        id: ContextId,
1068        cx: &mut ViewContext<Self>,
1069    ) -> Task<Result<View<ContextEditor>>> {
1070        let existing_context = self.pane.read(cx).items().find_map(|item| {
1071            item.downcast::<ContextEditor>()
1072                .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1073        });
1074        if let Some(existing_context) = existing_context {
1075            return cx.spawn(|this, mut cx| async move {
1076                this.update(&mut cx, |this, cx| {
1077                    this.show_context(existing_context.clone(), cx)
1078                })?;
1079                Ok(existing_context)
1080            });
1081        }
1082
1083        let context = self
1084            .context_store
1085            .update(cx, |store, cx| store.open_remote_context(id, cx));
1086        let fs = self.fs.clone();
1087        let workspace = self.workspace.clone();
1088        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1089            .log_err()
1090            .flatten();
1091
1092        cx.spawn(|this, mut cx| async move {
1093            let context = context.await?;
1094            let assistant_panel = this.clone();
1095            this.update(&mut cx, |this, cx| {
1096                let editor = cx.new_view(|cx| {
1097                    ContextEditor::for_context(
1098                        context,
1099                        fs,
1100                        workspace,
1101                        this.project.clone(),
1102                        lsp_adapter_delegate,
1103                        assistant_panel,
1104                        cx,
1105                    )
1106                });
1107                this.show_context(editor.clone(), cx);
1108                anyhow::Ok(editor)
1109            })?
1110        })
1111    }
1112
1113    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1114        LanguageModelRegistry::read_global(cx)
1115            .active_provider()
1116            .map_or(false, |provider| provider.is_authenticated(cx))
1117    }
1118
1119    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1120        LanguageModelRegistry::read_global(cx)
1121            .active_provider()
1122            .map_or(None, |provider| Some(provider.authenticate(cx)))
1123    }
1124
1125    fn restart_context_servers(
1126        workspace: &mut Workspace,
1127        _action: &context_server::Restart,
1128        cx: &mut ViewContext<Workspace>,
1129    ) {
1130        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
1131            return;
1132        };
1133
1134        assistant_panel.update(cx, |assistant_panel, cx| {
1135            assistant_panel
1136                .context_store
1137                .update(cx, |context_store, cx| {
1138                    context_store.restart_context_servers(cx);
1139                });
1140        });
1141    }
1142}
1143
1144impl Render for AssistantPanel {
1145    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1146        let mut registrar = DivRegistrar::new(
1147            |panel, cx| {
1148                panel
1149                    .pane
1150                    .read(cx)
1151                    .toolbar()
1152                    .read(cx)
1153                    .item_of_type::<BufferSearchBar>()
1154            },
1155            cx,
1156        );
1157        BufferSearchBar::register(&mut registrar);
1158        let registrar = registrar.into_div();
1159
1160        v_flex()
1161            .key_context("AssistantPanel")
1162            .size_full()
1163            .on_action(cx.listener(|this, _: &NewContext, cx| {
1164                this.new_context(cx);
1165            }))
1166            .on_action(
1167                cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1168            )
1169            .on_action(cx.listener(AssistantPanel::deploy_history))
1170            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1171            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1172            .child(registrar.size_full().child(self.pane.clone()))
1173            .into_any_element()
1174    }
1175}
1176
1177impl Panel for AssistantPanel {
1178    fn persistent_name() -> &'static str {
1179        "AssistantPanel"
1180    }
1181
1182    fn position(&self, cx: &WindowContext) -> DockPosition {
1183        match AssistantSettings::get_global(cx).dock {
1184            AssistantDockPosition::Left => DockPosition::Left,
1185            AssistantDockPosition::Bottom => DockPosition::Bottom,
1186            AssistantDockPosition::Right => DockPosition::Right,
1187        }
1188    }
1189
1190    fn position_is_valid(&self, _: DockPosition) -> bool {
1191        true
1192    }
1193
1194    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1195        settings::update_settings_file::<AssistantSettings>(
1196            self.fs.clone(),
1197            cx,
1198            move |settings, _| {
1199                let dock = match position {
1200                    DockPosition::Left => AssistantDockPosition::Left,
1201                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1202                    DockPosition::Right => AssistantDockPosition::Right,
1203                };
1204                settings.set_dock(dock);
1205            },
1206        );
1207    }
1208
1209    fn size(&self, cx: &WindowContext) -> Pixels {
1210        let settings = AssistantSettings::get_global(cx);
1211        match self.position(cx) {
1212            DockPosition::Left | DockPosition::Right => {
1213                self.width.unwrap_or(settings.default_width)
1214            }
1215            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1216        }
1217    }
1218
1219    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1220        match self.position(cx) {
1221            DockPosition::Left | DockPosition::Right => self.width = size,
1222            DockPosition::Bottom => self.height = size,
1223        }
1224        cx.notify();
1225    }
1226
1227    fn is_zoomed(&self, cx: &WindowContext) -> bool {
1228        self.pane.read(cx).is_zoomed()
1229    }
1230
1231    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1232        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1233    }
1234
1235    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1236        if active {
1237            if self.pane.read(cx).items_len() == 0 {
1238                self.new_context(cx);
1239            }
1240
1241            self.ensure_authenticated(cx);
1242        }
1243    }
1244
1245    fn pane(&self) -> Option<View<Pane>> {
1246        Some(self.pane.clone())
1247    }
1248
1249    fn remote_id() -> Option<proto::PanelId> {
1250        Some(proto::PanelId::AssistantPanel)
1251    }
1252
1253    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1254        let settings = AssistantSettings::get_global(cx);
1255        if !settings.enabled || !settings.button {
1256            return None;
1257        }
1258
1259        Some(IconName::ZedAssistant)
1260    }
1261
1262    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1263        Some("Assistant Panel")
1264    }
1265
1266    fn toggle_action(&self) -> Box<dyn Action> {
1267        Box::new(ToggleFocus)
1268    }
1269
1270    fn activation_priority(&self) -> u32 {
1271        4
1272    }
1273}
1274
1275impl EventEmitter<PanelEvent> for AssistantPanel {}
1276impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1277
1278impl FocusableView for AssistantPanel {
1279    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1280        self.pane.focus_handle(cx)
1281    }
1282}
1283
1284struct PromptLibraryInlineAssist;
1285
1286impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1287    fn assist(
1288        &self,
1289        prompt_editor: &View<Editor>,
1290        initial_prompt: Option<String>,
1291        cx: &mut ViewContext<PromptLibrary>,
1292    ) {
1293        InlineAssistant::update_global(cx, |assistant, cx| {
1294            assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
1295        })
1296    }
1297
1298    fn focus_assistant_panel(
1299        &self,
1300        workspace: &mut Workspace,
1301        cx: &mut ViewContext<Workspace>,
1302    ) -> bool {
1303        workspace.focus_panel::<AssistantPanel>(cx).is_some()
1304    }
1305}
1306
1307#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1308pub enum WorkflowAssistStatus {
1309    Pending,
1310    Confirmed,
1311    Done,
1312    Idle,
1313}
1314
1315pub struct ConfigurationView {
1316    focus_handle: FocusHandle,
1317    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
1318    _registry_subscription: Subscription,
1319}
1320
1321impl ConfigurationView {
1322    fn new(cx: &mut ViewContext<Self>) -> Self {
1323        let focus_handle = cx.focus_handle();
1324
1325        let registry_subscription = cx.subscribe(
1326            &LanguageModelRegistry::global(cx),
1327            |this, _, event: &language_model::Event, cx| match event {
1328                language_model::Event::AddedProvider(provider_id) => {
1329                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
1330                    if let Some(provider) = provider {
1331                        this.add_configuration_view(&provider, cx);
1332                    }
1333                }
1334                language_model::Event::RemovedProvider(provider_id) => {
1335                    this.remove_configuration_view(provider_id);
1336                }
1337                _ => {}
1338            },
1339        );
1340
1341        let mut this = Self {
1342            focus_handle,
1343            configuration_views: HashMap::default(),
1344            _registry_subscription: registry_subscription,
1345        };
1346        this.build_configuration_views(cx);
1347        this
1348    }
1349
1350    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
1351        let providers = LanguageModelRegistry::read_global(cx).providers();
1352        for provider in providers {
1353            self.add_configuration_view(&provider, cx);
1354        }
1355    }
1356
1357    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
1358        self.configuration_views.remove(provider_id);
1359    }
1360
1361    fn add_configuration_view(
1362        &mut self,
1363        provider: &Arc<dyn LanguageModelProvider>,
1364        cx: &mut ViewContext<Self>,
1365    ) {
1366        let configuration_view = provider.configuration_view(cx);
1367        self.configuration_views
1368            .insert(provider.id(), configuration_view);
1369    }
1370
1371    fn render_provider_view(
1372        &mut self,
1373        provider: &Arc<dyn LanguageModelProvider>,
1374        cx: &mut ViewContext<Self>,
1375    ) -> Div {
1376        let provider_id = provider.id().0.clone();
1377        let provider_name = provider.name().0.clone();
1378        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
1379
1380        let open_new_context = cx.listener({
1381            let provider = provider.clone();
1382            move |_, _, cx| {
1383                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
1384                    provider.clone(),
1385                ))
1386            }
1387        });
1388
1389        v_flex()
1390            .gap_2()
1391            .child(
1392                h_flex()
1393                    .justify_between()
1394                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
1395                    .when(provider.is_authenticated(cx), move |this| {
1396                        this.child(
1397                            h_flex().justify_end().child(
1398                                Button::new(
1399                                    SharedString::from(format!("new-context-{provider_id}")),
1400                                    "Open New Chat",
1401                                )
1402                                .icon_position(IconPosition::Start)
1403                                .icon(IconName::Plus)
1404                                .style(ButtonStyle::Filled)
1405                                .layer(ElevationIndex::ModalSurface)
1406                                .on_click(open_new_context),
1407                            ),
1408                        )
1409                    }),
1410            )
1411            .child(
1412                div()
1413                    .p(DynamicSpacing::Base08.rems(cx))
1414                    .bg(cx.theme().colors().surface_background)
1415                    .border_1()
1416                    .border_color(cx.theme().colors().border_variant)
1417                    .rounded_md()
1418                    .when(configuration_view.is_none(), |this| {
1419                        this.child(div().child(Label::new(format!(
1420                            "No configuration view for {}",
1421                            provider_name
1422                        ))))
1423                    })
1424                    .when_some(configuration_view, |this, configuration_view| {
1425                        this.child(configuration_view)
1426                    }),
1427            )
1428    }
1429}
1430
1431impl Render for ConfigurationView {
1432    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1433        let providers = LanguageModelRegistry::read_global(cx).providers();
1434        let provider_views = providers
1435            .into_iter()
1436            .map(|provider| self.render_provider_view(&provider, cx))
1437            .collect::<Vec<_>>();
1438
1439        let mut element = v_flex()
1440            .id("assistant-configuration-view")
1441            .track_focus(&self.focus_handle(cx))
1442            .bg(cx.theme().colors().editor_background)
1443            .size_full()
1444            .overflow_y_scroll()
1445            .child(
1446                v_flex()
1447                    .p(DynamicSpacing::Base16.rems(cx))
1448                    .border_b_1()
1449                    .border_color(cx.theme().colors().border)
1450                    .gap_1()
1451                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
1452                    .child(
1453                        Label::new(
1454                            "At least one LLM provider must be configured to use the Assistant.",
1455                        )
1456                        .color(Color::Muted),
1457                    ),
1458            )
1459            .child(
1460                v_flex()
1461                    .p(DynamicSpacing::Base16.rems(cx))
1462                    .mt_1()
1463                    .gap_6()
1464                    .flex_1()
1465                    .children(provider_views),
1466            )
1467            .into_any();
1468
1469        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
1470        // because we couldn't the element to take up the size of the parent.
1471        canvas(
1472            move |bounds, cx| {
1473                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
1474                element
1475            },
1476            |_, mut element, cx| {
1477                element.paint(cx);
1478            },
1479        )
1480        .flex_1()
1481        .w_full()
1482    }
1483}
1484
1485pub enum ConfigurationViewEvent {
1486    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
1487}
1488
1489impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
1490
1491impl FocusableView for ConfigurationView {
1492    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
1493        self.focus_handle.clone()
1494    }
1495}
1496
1497impl Item for ConfigurationView {
1498    type Event = ConfigurationViewEvent;
1499
1500    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
1501        Some("Configuration".into())
1502    }
1503}
1504
1505fn make_lsp_adapter_delegate(
1506    project: &Model<Project>,
1507    cx: &mut AppContext,
1508) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
1509    project.update(cx, |project, cx| {
1510        // TODO: Find the right worktree.
1511        let Some(worktree) = project.worktrees(cx).next() else {
1512            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
1513        };
1514        let http_client = project.client().http_client().clone();
1515        project.lsp_store().update(cx, |_, cx| {
1516            Ok(Some(LocalLspAdapterDelegate::new(
1517                project.languages().clone(),
1518                project.environment(),
1519                cx.weak_model(),
1520                &worktree,
1521                http_client,
1522                project.fs().clone(),
1523                cx,
1524            ) as Arc<dyn LspAdapterDelegate>))
1525        })
1526    })
1527}