diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 1944219117..c1127a5389 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -303,7 +303,7 @@ impl View for ProjectSearchView { // If Text -> Major: "Text search all files and folders", Minor: {...} let current_mode = self.current_mode; - let major_text = if model.pending_search.is_some() { + let mut major_text = if model.pending_search.is_some() { Cow::Borrowed("Searching...") } else if model.no_results.is_some_and(|v| v) { Cow::Borrowed("No Results") @@ -317,9 +317,18 @@ impl View for ProjectSearchView { } }; + let mut show_minor_text = true; let semantic_status = self.semantic_state.as_ref().and_then(|semantic| { let status = semantic.index_status; match status { + SemanticIndexStatus::NotAuthenticated => { + major_text = Cow::Borrowed("Not Authenticated"); + show_minor_text = false; + Some( + "API Key Missing: Please set 'OPENAI_API_KEY' in Environment Variables" + .to_string(), + ) + } SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), SemanticIndexStatus::Indexing { remaining_files, @@ -361,10 +370,13 @@ impl View for ProjectSearchView { let mut minor_text = Vec::new(); minor_text.push("".into()); minor_text.extend(semantic_status); - minor_text.push("Simply explain the code you are looking to find.".into()); - minor_text.push( - "ex. 'prompt user for permissions to index their project'".into(), - ); + if show_minor_text { + minor_text + .push("Simply explain the code you are looking to find.".into()); + minor_text.push( + "ex. 'prompt user for permissions to index their project'".into(), + ); + } minor_text } _ => vec![ diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index c808d33f23..b6ad75a34e 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -16,6 +16,7 @@ use embedding_queue::{EmbeddingQueue, FileToEmbed}; use futures::{future, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle}; use language::{Anchor, Bias, Buffer, Language, LanguageRegistry}; +use lazy_static::lazy_static; use ordered_float::OrderedFloat; use parking_lot::Mutex; use parsing::{CodeContextRetriever, Span, SpanDigest, PARSEABLE_ENTIRE_FILE_TYPES}; @@ -24,6 +25,7 @@ use project::{search::PathMatcher, Fs, PathChange, Project, ProjectEntryId, Work use smol::channel; use std::{ cmp::Reverse, + env, future::Future, mem, ops::Range, @@ -38,6 +40,10 @@ const SEMANTIC_INDEX_VERSION: usize = 11; const BACKGROUND_INDEXING_DELAY: Duration = Duration::from_secs(5 * 60); const EMBEDDING_QUEUE_FLUSH_TIMEOUT: Duration = Duration::from_millis(250); +lazy_static! { + static ref OPENAI_API_KEY: Option = env::var("OPENAI_API_KEY").ok(); +} + pub fn init( fs: Arc, http_client: Arc, @@ -100,6 +106,7 @@ pub fn init( #[derive(Copy, Clone, Debug)] pub enum SemanticIndexStatus { + NotAuthenticated, NotIndexed, Indexed, Indexing { @@ -274,7 +281,15 @@ impl SemanticIndex { settings::get::(cx).enabled } + pub fn has_api_key(&self) -> bool { + OPENAI_API_KEY.as_ref().is_some() + } + pub fn status(&self, project: &ModelHandle) -> SemanticIndexStatus { + if !self.has_api_key() { + return SemanticIndexStatus::NotAuthenticated; + } + if let Some(project_state) = self.projects.get(&project.downgrade()) { if project_state .worktrees @@ -694,12 +709,12 @@ impl SemanticIndex { let embedding_provider = self.embedding_provider.clone(); cx.spawn(|this, mut cx| async move { + index.await?; let query = embedding_provider .embed_batch(vec![query]) .await? .pop() .ok_or_else(|| anyhow!("could not embed query"))?; - index.await?; let search_start = Instant::now(); let modified_buffer_results = this.update(&mut cx, |this, cx| { @@ -965,6 +980,10 @@ impl SemanticIndex { project: ModelHandle, cx: &mut ModelContext, ) -> Task> { + if !self.has_api_key() { + return Task::ready(Err(anyhow!("no open ai key present"))); + } + if !self.projects.contains_key(&project.downgrade()) { let subscription = cx.subscribe(&project, |this, project, event, cx| match event { project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => {