use crate::slash_command::file_command::codeblock_fence_for_path;use crate::slash_command_working_set::SlashCommandWorkingSet;use crate::{ assistant_settings::{AssistantDockPosition, AssistantSettings}, humanize_token_count, prompt_library::open_prompt_library, prompts::PromptBuilder, slash_command::{ default_command::DefaultSlashCommand, docs_command::{DocsSlashCommand, DocsSlashCommandArgs}, file_command, SlashCommandCompletionProvider, }, slash_command_picker, terminal_inline_assistant::TerminalInlineAssistant, Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,};use anyhow::Result;use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};use assistant_tool::ToolWorkingSet;use client::{proto, zed_urls, Client, Status};use collections::{hash_map, BTreeSet, HashMap, HashSet};use editor::{ actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, display_map::{ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::{Autoscroll, AutoscrollStrategy}, Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt, ToOffset as _, ToPoint,};use editor::{display_map::CreaseId, FoldPlaceholder};use fs::Fs;use futures::FutureExt;use gpui::{ canvas, div, img, percentage, point, prelude::*, pulsating_between, size, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, UpdateGlobal, View, WeakModel, WeakView,};use indexed_docs::IndexedDocsStore;use language::{ language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset,};use language_model::{LanguageModelImage, LanguageModelToolUse};use language_model::{ LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, ZED_CLOUD_PROVIDER_ID,};use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};use multi_buffer::MultiBufferRow;use picker::{Picker, PickerDelegate};use project::lsp_store::LocalLspAdapterDelegate;use project::{Project, Worktree};use rope::Point;use search::{buffer_search::DivRegistrar, BufferSearchBar};use serde::{Deserialize, Serialize};use settings::{update_settings_file, Settings};use smol::stream::StreamExt;use std::{ any::TypeId, borrow::Cow, cmp, ops::{ControlFlow, Range}, path::PathBuf, sync::Arc, time::Duration,};use terminal_view::{terminal_panel::TerminalPanel, TerminalView};use text::SelectionGoal;use ui::{ prelude::*, utils::{format_distance_from_now, DateTimeType}, Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip,};use util::{maybe, ResultExt};use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane::{self, SaveIntent}, searchable::{SearchEvent, SearchableItem}, DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,};use workspace::{searchable::SearchableItemHandle, DraggedTab};use zed_actions::InlineAssist; pub fn init(cx: &mut AppContext) { workspace::FollowableViewRegistry::register::<ContextEditor>(cx); cx.observe_new_views( |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| { workspace .register_action(|workspace, _: &ToggleFocus, cx| { let settings = AssistantSettings::get_global(cx); if !settings.enabled { return; } workspace.toggle_panel_focus::<AssistantPanel>(cx); }) .register_action(AssistantPanel::inline_assist) .register_action(ContextEditor::quote_selection) .register_action(ContextEditor::insert_selection) .register_action(ContextEditor::copy_code) .register_action(ContextEditor::insert_dragged_files) .register_action(AssistantPanel::show_configuration) .register_action(AssistantPanel::create_new_context) .register_action(AssistantPanel::restart_context_servers); }, ) .detach(); cx.observe_new_views( |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| { let settings = AssistantSettings::get_global(cx); terminal_panel.asssistant_enabled(settings.enabled, cx); }, ) .detach();} pub enum AssistantPanelEvent { ContextEdited,} pub struct AssistantPanel { pane: View<Pane>, workspace: WeakView<Workspace>, width: Option<Pixels>, height: Option<Pixels>, project: Model<Project>, context_store: Model<ContextStore>, languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, subscriptions: Vec<Subscription>, model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, model_summary_editor: View<Editor>, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option<Subscription>, client_status: Option<client::Status>, watch_client_status: Option<Task<()>>, show_zed_ai_notice: bool,} #[derive(Clone)]enum ContextMetadata { Remote(RemoteContextMetadata), Saved(SavedContextMetadata),} struct SavedContextPickerDelegate { store: Model<ContextStore>, project: Model<Project>, matches: Vec<ContextMetadata>, selected_index: usize,} enum SavedContextPickerEvent { Confirmed(ContextMetadata),} enum InlineAssistTarget { Editor(View<Editor>, bool), Terminal(View<TerminalView>),} impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {} impl SavedContextPickerDelegate { fn new(project: Model<Project>, store: Model<ContextStore>) -> Self { Self { project, store, matches: Vec::new(), selected_index: 0, } }} impl PickerDelegate for SavedContextPickerDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) { self.selected_index = ix; } fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> { "Search...".into() } fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { let search = self.store.read(cx).search(query, cx); cx.spawn(|this, mut cx| async move { let matches = search.await; this.update(&mut cx, |this, cx| { let host_contexts = this.delegate.store.read(cx).host_contexts(); this.delegate.matches = host_contexts .iter() .cloned() .map(ContextMetadata::Remote) .chain(matches.into_iter().map(ContextMetadata::Saved)) .collect(); this.delegate.selected_index = 0; cx.notify(); }) .ok(); }) } fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) { if let Some(metadata) = self.matches.get(self.selected_index) { cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone())); } } fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {} fn render_match( &self, ix: usize, selected: bool, cx: &mut ViewContext<Picker<Self>>, ) -> Option<Self::ListItem> { let context = self.matches.get(ix)?; let item = match context { ContextMetadata::Remote(context) => { let host_user = self.project.read(cx).host().and_then(|collaborator| { self.project .read(cx) .user_store() .read(cx) .get_cached_user(collaborator.user_id) }); div() .flex() .w_full() .justify_between() .gap_2() .child( h_flex().flex_1().overflow_x_hidden().child( Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into())) .size(LabelSize::Small), ), ) .child( h_flex() .gap_2() .children(if let Some(host_user) = host_user { vec![ Avatar::new(host_user.avatar_uri.clone()).into_any_element(), Label::new(format!("Shared by @{}", host_user.github_login)) .color(Color::Muted) .size(LabelSize::Small) .into_any_element(), ] } else { vec![Label::new("Shared by host") .color(Color::Muted) .size(LabelSize::Small) .into_any_element()] }), ) } ContextMetadata::Saved(context) => div() .flex() .w_full() .justify_between() .gap_2() .child( h_flex() .flex_1() .child(Label::new(context.title.clone()).size(LabelSize::Small)) .overflow_x_hidden(), ) .child( Label::new(format_distance_from_now( DateTimeType::Local(context.mtime), false, true, true, )) .color(Color::Muted) .size(LabelSize::Small), ), }; Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child(item), ) }} impl AssistantPanel { pub fn load( workspace: WeakView<Workspace>, prompt_builder: Arc<PromptBuilder>, cx: AsyncWindowContext, ) -> Task<Result<View<Self>>> { cx.spawn(|mut cx| async move { let slash_commands = Arc::new(SlashCommandWorkingSet::default()); let tools = Arc::new(ToolWorkingSet::default()); let context_store = workspace .update(&mut cx, |workspace, cx| { let project = workspace.project().clone(); ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx) })? .await?; workspace.update(&mut cx, |workspace, cx| { // TODO: deserialize state. cx.new_view(|cx| Self::new(workspace, context_store, cx)) }) }) } fn new( workspace: &Workspace, context_store: Model<ContextStore>, cx: &mut ViewContext<Self>, ) -> Self { let model_selector_menu_handle = PopoverMenuHandle::default(); let model_summary_editor = cx.new_view(Editor::single_line); let context_editor_toolbar = cx.new_view(|cx| { ContextEditorToolbarItem::new( workspace, model_selector_menu_handle.clone(), model_summary_editor.clone(), cx, ) }); let pane = cx.new_view(|cx| { let mut pane = Pane::new( workspace.weak_handle(), workspace.project().clone(), Default::default(), None, NewContext.boxed_clone(), cx, ); let project = workspace.project().clone(); pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| { let action = maybe!({ if project.read(cx).is_local() { if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() { return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec())); } } let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() { if &tab.pane == cx.view() { return None; } let item = tab.pane.read(cx).item_for_index(tab.ix); Some( item.and_then(|item| item.project_path(cx)) .into_iter() .collect::<Vec<_>>(), ) } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>() { Some( selection .items() .filter_map(|item| { project.read(cx).path_for_entry(item.entry_id, cx) }) .collect::<Vec<_>>(), ) } else { None }?; let paths = project_paths .into_iter() .filter_map(|project_path| { let worktree = project .read(cx) .worktree_for_id(project_path.worktree_id, cx)?; let mut full_path = PathBuf::from(worktree.read(cx).root_name()); full_path.push(&project_path.path); Some(full_path) }) .collect::<Vec<_>>(); Some(InsertDraggedFiles::ProjectPaths(paths)) }); if let Some(action) = action { cx.dispatch_action(action.boxed_clone()); } ControlFlow::Break(()) }); pane.set_can_navigate(true, cx); pane.display_nav_history_buttons(None); pane.set_should_display_tab_bar(|_| true); pane.set_render_tab_bar_buttons(cx, move |pane, cx| { let focus_handle = pane.focus_handle(cx); let left_children = IconButton::new("history", IconName::HistoryRerun) .icon_size(IconSize::Small) .on_click(cx.listener({ let focus_handle = focus_handle.clone(); move |_, _, cx| { focus_handle.focus(cx); cx.dispatch_action(DeployHistory.boxed_clone()) } })) .tooltip({ let focus_handle = focus_handle.clone(); move |cx| { Tooltip::for_action_in( "Open History", &DeployHistory, &focus_handle, cx, ) } }) .toggle_state( pane.active_item() .map_or(false, |item| item.downcast::<ContextHistory>().is_some()), ); let _pane = cx.view().clone(); let right_children = h_flex() .gap(DynamicSpacing::Base02.rems(cx)) .child( IconButton::new("new-chat", IconName::Plus) .icon_size(IconSize::Small) .on_click( cx.listener(|_, _, cx| { cx.dispatch_action(NewContext.boxed_clone()) }), ) .tooltip(move |cx| { Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx) }), ) .child( PopoverMenu::new("assistant-panel-popover-menu") .trigger( IconButton::new("menu", IconName::EllipsisVertical) .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)), ) .menu(move |cx| { let zoom_label = if _pane.read(cx).is_zoomed() { "Zoom Out" } else { "Zoom In" }; let focus_handle = _pane.focus_handle(cx); Some(ContextMenu::build(cx, move |menu, _| { menu.context(focus_handle.clone()) .action("New Chat", Box::new(NewContext)) .action("History", Box::new(DeployHistory)) .action("Prompt Library", Box::new(DeployPromptLibrary)) .action("Configure", Box::new(ShowConfiguration)) .action(zoom_label, Box::new(ToggleZoom)) })) }), ) .into_any_element() .into(); (Some(left_children.into_any_element()), right_children) }); pane.toolbar().update(cx, |toolbar, cx| { toolbar.add_item(context_editor_toolbar.clone(), cx); toolbar.add_item(cx.new_view(BufferSearchBar::new), cx) }); pane }); let subscriptions = vec![Lines 1–500 of 5349. Load more
use crate::slash_command::file_command::codeblock_fence_for_path; use crate::slash_command_working_set::SlashCommandWorkingSet; use crate::{ assistant_settings::{AssistantDockPosition, AssistantSettings}, humanize_token_count, prompt_library::open_prompt_library, prompts::PromptBuilder, slash_command::{ default_command::DefaultSlashCommand, docs_command::{DocsSlashCommand, DocsSlashCommandArgs}, file_command, SlashCommandCompletionProvider, }, slash_command_picker, terminal_inline_assistant::TerminalInlineAssistant, Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector, }; use anyhow::Result; use assistant_slash_command::{SlashCommand, SlashCommandOutputSection}; use assistant_tool::ToolWorkingSet; use client::{proto, zed_urls, Client, Status}; use collections::{hash_map, BTreeSet, HashMap, HashSet}; use editor::{ actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt}, display_map::{ BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId, RenderBlock, ToDisplayPoint, }, scroll::{Autoscroll, AutoscrollStrategy}, Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt, ToOffset as _, ToPoint, }; use editor::{display_map::CreaseId, FoldPlaceholder}; use fs::Fs; use futures::FutureExt; use gpui::{ canvas, div, img, percentage, point, prelude::*, pulsating_between, size, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation, UpdateGlobal, View, WeakModel, WeakView, }; use indexed_docs::IndexedDocsStore; use language::{ language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset, }; use language_model::{LanguageModelImage, LanguageModelToolUse}; use language_model::{ LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, ZED_CLOUD_PROVIDER_ID, }; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use multi_buffer::MultiBufferRow; use picker::{Picker, PickerDelegate}; use project::lsp_store::LocalLspAdapterDelegate; use project::{Project, Worktree}; use rope::Point; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::{update_settings_file, Settings}; use smol::stream::StreamExt; use std::{ any::TypeId, borrow::Cow, cmp, ops::{ControlFlow, Range}, path::PathBuf, sync::Arc, time::Duration, }; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use text::SelectionGoal; use ui::{ prelude::*, utils::{format_distance_from_now, DateTimeType}, Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, }; use util::{maybe, ResultExt}; use workspace::{ dock::{DockPosition, Panel, PanelEvent}, item::{self, FollowableItem, Item, ItemHandle}, notifications::NotificationId, pane::{self, SaveIntent}, searchable::{SearchEvent, SearchableItem}, DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, }; use workspace::{searchable::SearchableItemHandle, DraggedTab}; use zed_actions::InlineAssist; pub fn init(cx: &mut AppContext) { workspace::FollowableViewRegistry::register::<ContextEditor>(cx); cx.observe_new_views( |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| { workspace .register_action(|workspace, _: &ToggleFocus, cx| { let settings = AssistantSettings::get_global(cx); if !settings.enabled { return; } workspace.toggle_panel_focus::<AssistantPanel>(cx); }) .register_action(AssistantPanel::inline_assist) .register_action(ContextEditor::quote_selection) .register_action(ContextEditor::insert_selection) .register_action(ContextEditor::copy_code) .register_action(ContextEditor::insert_dragged_files) .register_action(AssistantPanel::show_configuration) .register_action(AssistantPanel::create_new_context) .register_action(AssistantPanel::restart_context_servers); }, ) .detach(); cx.observe_new_views( |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| { let settings = AssistantSettings::get_global(cx); terminal_panel.asssistant_enabled(settings.enabled, cx); }, ) .detach(); } pub enum AssistantPanelEvent { ContextEdited, } pub struct AssistantPanel { pane: View<Pane>, workspace: WeakView<Workspace>, width: Option<Pixels>, height: Option<Pixels>, project: Model<Project>, context_store: Model<ContextStore>, languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, subscriptions: Vec<Subscription>, model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>, model_summary_editor: View<Editor>, authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>, configuration_subscription: Option<Subscription>, client_status: Option<client::Status>, watch_client_status: Option<Task<()>>, show_zed_ai_notice: bool, } #[derive(Clone)] enum ContextMetadata { Remote(RemoteContextMetadata), Saved(SavedContextMetadata), } struct SavedContextPickerDelegate { store: Model<ContextStore>, project: Model<Project>, matches: Vec<ContextMetadata>, selected_index: usize, } enum SavedContextPickerEvent { Confirmed(ContextMetadata), } enum InlineAssistTarget { Editor(View<Editor>, bool), Terminal(View<TerminalView>), } impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {} impl SavedContextPickerDelegate { fn new(project: Model<Project>, store: Model<ContextStore>) -> Self { Self { project, store, matches: Vec::new(), selected_index: 0, } } } impl PickerDelegate for SavedContextPickerDelegate { type ListItem = ListItem; fn match_count(&self) -> usize { self.matches.len() } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) { self.selected_index = ix; } fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> { "Search...".into() } fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> { let search = self.store.read(cx).search(query, cx); cx.spawn(|this, mut cx| async move { let matches = search.await; this.update(&mut cx, |this, cx| { let host_contexts = this.delegate.store.read(cx).host_contexts(); this.delegate.matches = host_contexts .iter() .cloned() .map(ContextMetadata::Remote) .chain(matches.into_iter().map(ContextMetadata::Saved)) .collect(); this.delegate.selected_index = 0; cx.notify(); }) .ok(); }) } fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) { if let Some(metadata) = self.matches.get(self.selected_index) { cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone())); } } fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {} fn render_match( &self, ix: usize, selected: bool, cx: &mut ViewContext<Picker<Self>>, ) -> Option<Self::ListItem> { let context = self.matches.get(ix)?; let item = match context { ContextMetadata::Remote(context) => { let host_user = self.project.read(cx).host().and_then(|collaborator| { self.project .read(cx) .user_store() .read(cx) .get_cached_user(collaborator.user_id) }); div() .flex() .w_full() .justify_between() .gap_2() .child( h_flex().flex_1().overflow_x_hidden().child( Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into())) .size(LabelSize::Small), ), ) .child( h_flex() .gap_2() .children(if let Some(host_user) = host_user { vec![ Avatar::new(host_user.avatar_uri.clone()).into_any_element(), Label::new(format!("Shared by @{}", host_user.github_login)) .color(Color::Muted) .size(LabelSize::Small) .into_any_element(), ] } else { vec![Label::new("Shared by host") .color(Color::Muted) .size(LabelSize::Small) .into_any_element()] }), ) } ContextMetadata::Saved(context) => div() .flex() .w_full() .justify_between() .gap_2() .child( h_flex() .flex_1() .child(Label::new(context.title.clone()).size(LabelSize::Small)) .overflow_x_hidden(), ) .child( Label::new(format_distance_from_now( DateTimeType::Local(context.mtime), false, true, true, )) .color(Color::Muted) .size(LabelSize::Small), ), }; Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child(item), ) } } impl AssistantPanel { pub fn load( workspace: WeakView<Workspace>, prompt_builder: Arc<PromptBuilder>, cx: AsyncWindowContext, ) -> Task<Result<View<Self>>> { cx.spawn(|mut cx| async move { let slash_commands = Arc::new(SlashCommandWorkingSet::default()); let tools = Arc::new(ToolWorkingSet::default()); let context_store = workspace .update(&mut cx, |workspace, cx| { let project = workspace.project().clone(); ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx) })? .await?; workspace.update(&mut cx, |workspace, cx| { // TODO: deserialize state. cx.new_view(|cx| Self::new(workspace, context_store, cx)) }) }) } fn new( workspace: &Workspace, context_store: Model<ContextStore>, cx: &mut ViewContext<Self>, ) -> Self { let model_selector_menu_handle = PopoverMenuHandle::default(); let model_summary_editor = cx.new_view(Editor::single_line); let context_editor_toolbar = cx.new_view(|cx| { ContextEditorToolbarItem::new( workspace, model_selector_menu_handle.clone(), model_summary_editor.clone(), cx, ) }); let pane = cx.new_view(|cx| { let mut pane = Pane::new( workspace.weak_handle(), workspace.project().clone(), Default::default(), None, NewContext.boxed_clone(), cx, ); let project = workspace.project().clone(); pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| { let action = maybe!({ if project.read(cx).is_local() { if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() { return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec())); } } let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>() { if &tab.pane == cx.view() { return None; } let item = tab.pane.read(cx).item_for_index(tab.ix); Some( item.and_then(|item| item.project_path(cx)) .into_iter() .collect::<Vec<_>>(), ) } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>() { Some( selection .items() .filter_map(|item| { project.read(cx).path_for_entry(item.entry_id, cx) }) .collect::<Vec<_>>(), ) } else { None }?; let paths = project_paths .into_iter() .filter_map(|project_path| { let worktree = project .read(cx) .worktree_for_id(project_path.worktree_id, cx)?; let mut full_path = PathBuf::from(worktree.read(cx).root_name()); full_path.push(&project_path.path); Some(full_path) }) .collect::<Vec<_>>(); Some(InsertDraggedFiles::ProjectPaths(paths)) }); if let Some(action) = action { cx.dispatch_action(action.boxed_clone()); } ControlFlow::Break(()) }); pane.set_can_navigate(true, cx); pane.display_nav_history_buttons(None); pane.set_should_display_tab_bar(|_| true); pane.set_render_tab_bar_buttons(cx, move |pane, cx| { let focus_handle = pane.focus_handle(cx); let left_children = IconButton::new("history", IconName::HistoryRerun) .icon_size(IconSize::Small) .on_click(cx.listener({ let focus_handle = focus_handle.clone(); move |_, _, cx| { focus_handle.focus(cx); cx.dispatch_action(DeployHistory.boxed_clone()) } })) .tooltip({ let focus_handle = focus_handle.clone(); move |cx| { Tooltip::for_action_in( "Open History", &DeployHistory, &focus_handle, cx, ) } }) .toggle_state( pane.active_item() .map_or(false, |item| item.downcast::<ContextHistory>().is_some()), ); let _pane = cx.view().clone(); let right_children = h_flex() .gap(DynamicSpacing::Base02.rems(cx)) .child( IconButton::new("new-chat", IconName::Plus) .icon_size(IconSize::Small) .on_click( cx.listener(|_, _, cx| { cx.dispatch_action(NewContext.boxed_clone()) }), ) .tooltip(move |cx| { Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx) }), ) .child( PopoverMenu::new("assistant-panel-popover-menu") .trigger( IconButton::new("menu", IconName::EllipsisVertical) .icon_size(IconSize::Small) .tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)), ) .menu(move |cx| { let zoom_label = if _pane.read(cx).is_zoomed() { "Zoom Out" } else { "Zoom In" }; let focus_handle = _pane.focus_handle(cx); Some(ContextMenu::build(cx, move |menu, _| { menu.context(focus_handle.clone()) .action("New Chat", Box::new(NewContext)) .action("History", Box::new(DeployHistory)) .action("Prompt Library", Box::new(DeployPromptLibrary)) .action("Configure", Box::new(ShowConfiguration)) .action(zoom_label, Box::new(ToggleZoom)) })) }), ) .into_any_element() .into(); (Some(left_children.into_any_element()), right_children) }); pane.toolbar().update(cx, |toolbar, cx| { toolbar.add_item(context_editor_toolbar.clone(), cx); toolbar.add_item(cx.new_view(BufferSearchBar::new), cx) }); pane }); let subscriptions = vec![