diff --git a/crates/ai/src/embedding.rs b/crates/ai/src/embedding.rs index 332470aa54..510f987cca 100644 --- a/crates/ai/src/embedding.rs +++ b/crates/ai/src/embedding.rs @@ -290,7 +290,7 @@ impl EmbeddingProvider for OpenAIEmbeddings { let mut request_number = 0; let mut rate_limiting = false; - let mut request_timeout: u64 = 15; + let mut request_timeout: u64 = 30; let mut response: Response; while request_number < MAX_RETRIES { response = self diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index e6c120cd64..c49d60b8ee 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -24,10 +24,10 @@ use futures::StreamExt; use gpui::{ actions, elements::{ - ChildView, Component, Empty, Flex, Label, MouseEventHandler, ParentElement, SafeStylable, - Stack, Svg, Text, UniformList, UniformListState, + ChildView, Component, Empty, Flex, Label, LabelStyle, MouseEventHandler, ParentElement, + SafeStylable, Stack, Svg, Text, UniformList, UniformListState, }, - fonts::HighlightStyle, + fonts::{HighlightStyle, TextStyle}, geometry::vector::{vec2f, Vector2F}, platform::{CursorStyle, MouseButton, PromptLevel}, Action, AnyElement, AppContext, AsyncAppContext, ClipboardItem, Element, Entity, ModelContext, @@ -37,7 +37,7 @@ use gpui::{ use language::{language_settings::SoftWrap, Buffer, LanguageRegistry, ToOffset as _}; use project::Project; use search::BufferSearchBar; -use semantic_index::SemanticIndex; +use semantic_index::{SemanticIndex, SemanticIndexStatus}; use settings::SettingsStore; use std::{ cell::{Cell, RefCell}, @@ -2756,6 +2756,7 @@ struct InlineAssistant { semantic_index: Option>, semantic_permissioned: Option, project: ModelHandle, + maintain_rate_limit: Option>, } impl Entity for InlineAssistant { @@ -2772,67 +2773,65 @@ impl View for InlineAssistant { let theme = theme::current(cx); Flex::row() - .with_child( - Flex::row() - .with_child( - Button::action(ToggleIncludeConversation) - .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_children([Flex::row() + .with_child( + Button::action(ToggleIncludeConversation) + .with_tooltip("Include Conversation", theme.tooltip.clone()) + .with_id(self.id) + .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) + .toggleable(self.include_conversation) + .with_style(theme.assistant.inline.include_conversation.clone()) + .element() + .aligned(), + ) + .with_children(if SemanticIndex::enabled(cx) { + Some( + Button::action(ToggleRetrieveContext) + .with_tooltip("Retrieve Context", theme.tooltip.clone()) .with_id(self.id) - .with_contents(theme::components::svg::Svg::new("icons/ai.svg")) - .toggleable(self.include_conversation) - .with_style(theme.assistant.inline.include_conversation.clone()) + .with_contents(theme::components::svg::Svg::new( + "icons/magnifying_glass.svg", + )) + .toggleable(self.retrieve_context) + .with_style(theme.assistant.inline.retrieve_context.clone()) .element() .aligned(), ) - .with_children(if SemanticIndex::enabled(cx) { - Some( - Button::action(ToggleRetrieveContext) - .with_tooltip("Retrieve Context", theme.tooltip.clone()) - .with_id(self.id) - .with_contents(theme::components::svg::Svg::new( - "icons/magnifying_glass.svg", - )) - .toggleable(self.retrieve_context) - .with_style(theme.assistant.inline.retrieve_context.clone()) - .element() - .aligned(), - ) - } else { - None - }) - .with_children(if let Some(error) = self.codegen.read(cx).error() { - Some( - Svg::new("icons/error.svg") - .with_color(theme.assistant.error_icon.color) - .constrained() - .with_width(theme.assistant.error_icon.width) - .contained() - .with_style(theme.assistant.error_icon.container) - .with_tooltip::( - self.id, - error.to_string(), - None, - theme.tooltip.clone(), - cx, - ) - .aligned(), - ) - } else { - None - }) - .aligned() - .constrained() - .dynamically({ - let measurements = self.measurements.clone(); - move |constraint, _, _| { - let measurements = measurements.get(); - SizeConstraint { - min: vec2f(measurements.gutter_width, constraint.min.y()), - max: vec2f(measurements.gutter_width, constraint.max.y()), - } + } else { + None + }) + .with_children(if let Some(error) = self.codegen.read(cx).error() { + Some( + Svg::new("icons/error.svg") + .with_color(theme.assistant.error_icon.color) + .constrained() + .with_width(theme.assistant.error_icon.width) + .contained() + .with_style(theme.assistant.error_icon.container) + .with_tooltip::( + self.id, + error.to_string(), + None, + theme.tooltip.clone(), + cx, + ) + .aligned(), + ) + } else { + None + }) + .aligned() + .constrained() + .dynamically({ + let measurements = self.measurements.clone(); + move |constraint, _, _| { + let measurements = measurements.get(); + SizeConstraint { + min: vec2f(measurements.gutter_width, constraint.min.y()), + max: vec2f(measurements.gutter_width, constraint.max.y()), } - }), - ) + } + })]) .with_child(Empty::new().constrained().dynamically({ let measurements = self.measurements.clone(); move |constraint, _, _| { @@ -2855,6 +2854,19 @@ impl View for InlineAssistant { .left() .flex(1., true), ) + .with_children(if self.retrieve_context { + Some( + Flex::row() + .with_child(Label::new( + self.retrieve_context_status(cx), + theme.assistant.inline.context_status.text.clone(), + )) + .flex(1., true) + .aligned(), + ) + } else { + None + }) .contained() .with_style(theme.assistant.inline.container) .into_any() @@ -2896,11 +2908,15 @@ impl InlineAssistant { editor.set_placeholder_text(placeholder, cx); editor }); - let subscriptions = vec![ + let mut subscriptions = vec![ cx.observe(&codegen, Self::handle_codegen_changed), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), ]; + if let Some(semantic_index) = semantic_index.clone() { + subscriptions.push(cx.observe(&semantic_index, Self::semantic_index_changed)); + } + Self { id, prompt_editor, @@ -2918,6 +2934,7 @@ impl InlineAssistant { semantic_permissioned: None, semantic_index, project, + maintain_rate_limit: None, } } @@ -2947,6 +2964,34 @@ impl InlineAssistant { } } + fn semantic_index_changed( + &mut self, + semantic_index: ModelHandle, + cx: &mut ViewContext, + ) { + let project = self.project.clone(); + let status = semantic_index.read(cx).status(&project); + match status { + SemanticIndexStatus::Indexing { + rate_limit_expiry: Some(_), + .. + } => { + if self.maintain_rate_limit.is_none() { + self.maintain_rate_limit = Some(cx.spawn(|this, mut cx| async move { + loop { + cx.background().timer(Duration::from_secs(1)).await; + this.update(&mut cx, |_, cx| cx.notify()).log_err(); + } + })); + } + return; + } + _ => { + self.maintain_rate_limit = None; + } + } + } + fn handle_codegen_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { let is_read_only = !self.codegen.read(cx).idle(); self.prompt_editor.update(cx, |editor, cx| { @@ -3044,16 +3089,7 @@ impl InlineAssistant { cx.emit(InlineAssistantEvent::RetrieveContextToggled { retrieve_context: this.retrieve_context, }); - - if this.retrieve_context { - let context_status = this.retrieve_context_status(cx); - if let Some(workspace) = this.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - workspace.show_toast(Toast::new(0, context_status), cx) - }); - } - } - + this.index_project(project, cx).log_err(); cx.notify(); })?; @@ -3062,6 +3098,18 @@ impl InlineAssistant { .detach_and_log_err(cx); } + fn index_project( + &self, + project: ModelHandle, + cx: &mut ViewContext, + ) -> anyhow::Result<()> { + if let Some(semantic_index) = self.semantic_index.clone() { + let _ = semantic_index.update(cx, |index, cx| index.index_project(project, cx)); + } + + anyhow::Ok(()) + } + fn retrieve_context_status(&self, cx: &mut ViewContext) -> String { let project = self.project.clone(); if let Some(semantic_index) = self.semantic_index.clone() { @@ -3072,23 +3120,23 @@ impl InlineAssistant { "Not Authenticated!\nPlease ensure you have an `OPENAI_API_KEY` in your environment variables.".to_string() } semantic_index::SemanticIndexStatus::Indexed => { - "Indexing for Context Retrieval Complete!".to_string() + "Indexing Complete!".to_string() } semantic_index::SemanticIndexStatus::Indexing { remaining_files, rate_limit_expiry } => { - let mut status = format!("Indexing for Context Retrieval...\nRemaining files to index: {remaining_files}"); + let mut status = format!("Remaining files to index for Context Retrieval: {remaining_files}"); if let Some(rate_limit_expiry) = rate_limit_expiry { let remaining_seconds = rate_limit_expiry.duration_since(Instant::now()); if remaining_seconds > Duration::from_secs(0) { - writeln!(status, "Rate limit resets in {}s", remaining_seconds.as_secs()).unwrap(); + write!(status, " (rate limit resets in {}s)", remaining_seconds.as_secs()).unwrap(); } } status } - _ => { - "Indexing for Context Retrieval...\nRemaining files to index: 48".to_string() + semantic_index::SemanticIndexStatus::NotIndexed => { + "Not Indexed for Context Retrieval".to_string() } }; } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 600ac7f14a..4ed32b6d1b 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -1191,6 +1191,7 @@ pub struct InlineAssistantStyle { pub pending_edit_background: Color, pub include_conversation: ToggleIconButtonStyle, pub retrieve_context: ToggleIconButtonStyle, + pub context_status: ContainedText, } #[derive(Clone, Deserialize, Default, JsonSchema)] diff --git a/styles/src/style_tree/assistant.ts b/styles/src/style_tree/assistant.ts index 7fd1388d9c..7e7b597956 100644 --- a/styles/src/style_tree/assistant.ts +++ b/styles/src/style_tree/assistant.ts @@ -79,6 +79,9 @@ export default function assistant(): any { }, }, pending_edit_background: background(theme.highest, "positive"), + context_status: { + ...text(theme.highest, "mono", "disabled", { size: "sm" }), + }, retrieve_context: toggleable({ base: interactive({ base: {