diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 69587f856e94e776618b9c7a8846fb3ba726b38c..b147ea4ea066c3160fcda28b72f6776839949765 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -322,7 +322,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") @@ -336,9 +336,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, @@ -380,10 +389,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/embedding.rs b/crates/semantic_index/src/embedding.rs index b0124bf7df2664f1b3f237edd601a8e59b196fbd..2b6e94854e2c0e8e21f9df11874f45d35dca8a59 100644 --- a/crates/semantic_index/src/embedding.rs +++ b/crates/semantic_index/src/embedding.rs @@ -117,6 +117,7 @@ struct OpenAIEmbeddingUsage { #[async_trait] pub trait EmbeddingProvider: Sync + Send { + fn is_authenticated(&self) -> bool; async fn embed_batch(&self, spans: Vec) -> Result>; fn max_tokens_per_batch(&self) -> usize; fn truncate(&self, span: &str) -> (String, usize); @@ -127,6 +128,9 @@ pub struct DummyEmbeddings {} #[async_trait] impl EmbeddingProvider for DummyEmbeddings { + fn is_authenticated(&self) -> bool { + true + } fn rate_limit_expiration(&self) -> Option { None } @@ -229,6 +233,9 @@ impl OpenAIEmbeddings { #[async_trait] impl EmbeddingProvider for OpenAIEmbeddings { + fn is_authenticated(&self) -> bool { + OPENAI_API_KEY.as_ref().is_some() + } fn max_tokens_per_batch(&self) -> usize { 50000 } diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index c808d33f23a6e08858aae9de0aa4b893c40921df..1ba0001cfda9c6be7128e1378bc75750cd1842da 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 { @@ -275,6 +282,10 @@ impl SemanticIndex { } pub fn status(&self, project: &ModelHandle) -> SemanticIndexStatus { + if !self.embedding_provider.is_authenticated() { + return SemanticIndexStatus::NotAuthenticated; + } + if let Some(project_state) = self.projects.get(&project.downgrade()) { if project_state .worktrees @@ -694,12 +705,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 +976,10 @@ impl SemanticIndex { project: ModelHandle, cx: &mut ModelContext, ) -> Task> { + if !self.embedding_provider.is_authenticated() { + return Task::ready(Err(anyhow!("user is not authenticated"))); + } + if !self.projects.contains_key(&project.downgrade()) { let subscription = cx.subscribe(&project, |this, project, event, cx| match event { project::Event::WorktreeAdded | project::Event::WorktreeRemoved(_) => { diff --git a/crates/semantic_index/src/semantic_index_tests.rs b/crates/semantic_index/src/semantic_index_tests.rs index 9035327b2e8d910312ebb2f7c117a51d300f5d6a..f386665915b1d9b9314c7246da93daec6624900e 100644 --- a/crates/semantic_index/src/semantic_index_tests.rs +++ b/crates/semantic_index/src/semantic_index_tests.rs @@ -1267,6 +1267,9 @@ impl FakeEmbeddingProvider { #[async_trait] impl EmbeddingProvider for FakeEmbeddingProvider { + fn is_authenticated(&self) -> bool { + true + } fn truncate(&self, span: &str) -> (String, usize) { (span.to_string(), 1) }