Add local project indexing and search guidance
This commit is contained in:
@@ -276,6 +276,8 @@ pub fn initialize_app(app: &mut App) {
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(FileSearchModel::new);
|
||||
app.add_singleton_model(RepoOutlines::new_for_test);
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
app.add_singleton_model(ai::index::local_project_index::LocalProjectIndexManager::new);
|
||||
#[cfg(feature = "voice_input")]
|
||||
app.add_singleton_model(voice_input::VoiceInput::new);
|
||||
app.add_singleton_model(|ctx| {
|
||||
|
||||
+79
-24
@@ -71,6 +71,8 @@ use action::RememberForWormholing;
|
||||
pub use action::{AgentOnboardingVersion, OnboardingIntention, OnboardingVersion, TerminalAction};
|
||||
use ai::api_keys::{ApiKeyManager, AwsCredentialsState};
|
||||
use ai::index::full_source_code_embedding::manager::{BuildSource, CodebaseIndexManager};
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
use ai::index::local_project_index::LocalProjectIndexManager;
|
||||
use async_channel::{Receiver, Sender};
|
||||
use base64::Engine as _;
|
||||
use block_banner::{render_wormholing_banner, WormholeBannerState};
|
||||
@@ -7362,6 +7364,15 @@ impl TerminalView {
|
||||
BlocklistAIActionEvent::InitProject(_) => {
|
||||
self.on_next_conversation_finished(|me, _reason, ctx| {
|
||||
if let Some(path) = me.pwd() {
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx)
|
||||
.update(ctx, |manager, ctx| {
|
||||
manager.index_directory(PathBuf::from(path), ctx)
|
||||
})
|
||||
{
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
}
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(PathBuf::from(path), ctx);
|
||||
});
|
||||
@@ -10619,7 +10630,16 @@ impl TerminalView {
|
||||
});
|
||||
}
|
||||
|
||||
// Index the codebase
|
||||
// Index the project.
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx)
|
||||
.update(ctx, |manager, ctx| {
|
||||
manager.index_directory(banner_state.repo_path.clone(), ctx)
|
||||
})
|
||||
{
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
}
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(banner_state.repo_path.clone(), ctx);
|
||||
});
|
||||
@@ -13835,6 +13855,13 @@ impl TerminalView {
|
||||
self.update_focused_terminal_info(ctx);
|
||||
|
||||
if let Some(working_directory) = self.active_session_path_if_local(ctx) {
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(working_directory.clone(), ctx)
|
||||
}) {
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
}
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, _ctx| {
|
||||
manager.handle_session_bootstrapped(&working_directory);
|
||||
});
|
||||
@@ -14200,10 +14227,7 @@ impl TerminalView {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(pwd_path) = self
|
||||
.pwd()
|
||||
.and_then(|pwd| Path::new(&pwd).canonicalize().ok())
|
||||
else {
|
||||
let Some(pwd_path) = self.active_session_path_if_local(ctx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -14246,6 +14270,18 @@ impl TerminalView {
|
||||
me.redetermine_terminal_focus(ctx);
|
||||
}
|
||||
InitProjectModelEvent::StepCompleted(_) => {}
|
||||
InitProjectModelEvent::CodebaseIndexFailed(message) => {
|
||||
let window_id = ctx.window_id();
|
||||
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
|
||||
toast_stack.add_ephemeral_toast(
|
||||
DismissibleToast::error(format!(
|
||||
"Local project indexing failed: {message}"
|
||||
)),
|
||||
window_id,
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
}
|
||||
InitProjectModelEvent::Cancelled => {
|
||||
me.active_init_project_model = None;
|
||||
// Mark conversation as cancelled
|
||||
@@ -26002,6 +26038,13 @@ impl TerminalView {
|
||||
return;
|
||||
};
|
||||
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(active_session_path.clone(), ctx)
|
||||
}) {
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
}
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.build_and_sync_codebase_index(
|
||||
BuildSource::FromPath(active_session_path.as_path()),
|
||||
@@ -26011,7 +26054,7 @@ impl TerminalView {
|
||||
}
|
||||
|
||||
fn write_codebase_index(&self, _ctx: &mut ViewContext<Self>) {
|
||||
#[cfg(feature = "local_fs")]
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
{
|
||||
let Some(working_directory_str) = self.pwd() else {
|
||||
log::error!("No working directory found for terminal session");
|
||||
@@ -27431,25 +27474,37 @@ impl TypedActionView for TerminalView {
|
||||
}
|
||||
SummarizeConversation => self.summarize_conversation(ctx),
|
||||
IndexProjectSpeedbump => {
|
||||
let codebase_context_enabled =
|
||||
UserWorkspaces::as_ref(ctx).is_codebase_context_enabled(ctx);
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Some(repo_path) = self
|
||||
.active_session_path_if_local(ctx)
|
||||
.and_then(|path| dunce::canonicalize(path).ok())
|
||||
{
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx)
|
||||
.update(ctx, |manager, ctx| {
|
||||
manager.index_directory(repo_path.clone(), ctx)
|
||||
})
|
||||
{
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
}
|
||||
self.remove_codebase_index_speedbump_banner(ctx);
|
||||
self.insert_codebase_index_speedbump_banner(
|
||||
repo_path, true, /* show_is_indexing */
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
if FeatureFlag::FullSourceCodeEmbedding.is_enabled() && codebase_context_enabled {
|
||||
#[cfg(feature = "local_fs")]
|
||||
if let Some(current_dir) = self.pwd() {
|
||||
let directory = PathBuf::from(¤t_dir);
|
||||
|
||||
if let Ok(repo_path) = directory.canonicalize() {
|
||||
// Start indexing the codebase
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(repo_path.clone(), ctx);
|
||||
});
|
||||
|
||||
self.remove_codebase_index_speedbump_banner(ctx);
|
||||
self.insert_codebase_index_speedbump_banner(
|
||||
repo_path, true, /* show_is_indexing */
|
||||
ctx,
|
||||
);
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
{
|
||||
let codebase_context_enabled =
|
||||
UserWorkspaces::as_ref(ctx).is_codebase_context_enabled(ctx);
|
||||
if codebase_context_enabled {
|
||||
if let Some(current_dir) = self.pwd() {
|
||||
let directory = PathBuf::from(¤t_dir);
|
||||
if let Ok(repo_path) = directory.canonicalize() {
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(repo_path.clone(), ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ pub mod model;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
use ai::index::full_source_code_embedding::manager::CodebaseIndexManager;
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
use ai::index::local_project_index::LocalProjectIndexManager;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use lsp::supported_servers::LSPServerType;
|
||||
use lsp_server_selector::{create_lsp_server_selector, LSPServerInfo};
|
||||
@@ -40,7 +43,7 @@ use crate::view_components::DismissibleToast;
|
||||
use crate::workspace::ToastStack;
|
||||
use crate::{send_telemetry_from_ctx, TelemetryEvent};
|
||||
|
||||
const ONBOARDING_TEXT: &str = "Great - let's begin setting up this project! Would you like to give me permission to index this codebase? It allows me to quickly understand context and provide more targeted solutions when working in this codebase. No code is stored on Galaxy servers.";
|
||||
const ONBOARDING_TEXT: &str = "Great - let's begin setting up this project! Would you like to let Galaxy build a local project index? It allows me to quickly understand context and provide more targeted solutions while keeping the index on your device.";
|
||||
const ALREADY_SETUP_TEXT: &str = "It looks like this project has already been initialized. You can re-generate the GALAXY.md for this codebase by clicking the button below.";
|
||||
// Native Galaxy rules file format.
|
||||
pub const FILES_TO_CHECK: [&str; 4] = ["GALAXY.md", "AGENTS.md", "WARP.md", "CLAUDE.md"];
|
||||
@@ -644,10 +647,11 @@ impl InitStepBlock {
|
||||
.render(app)
|
||||
.finish()
|
||||
}
|
||||
InitStepStatus::Running => {
|
||||
// Codebase context doesn't have a "running" state
|
||||
Empty::new().finish()
|
||||
}
|
||||
InitStepStatus::Running => RenderableAction::new("Building local project index", app)
|
||||
.with_icon(in_progress_icon(Appearance::as_ref(app)).finish())
|
||||
.with_content_item_spacing()
|
||||
.render(app)
|
||||
.finish(),
|
||||
InitStepStatus::Completed(result) => {
|
||||
self.render_completed_codebase_context(result, app)
|
||||
}
|
||||
@@ -1053,15 +1057,30 @@ impl TypedActionView for InitStepBlock {
|
||||
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
|
||||
match action {
|
||||
InitProjectBlockAction::IndexCodebase(directory) => {
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
if let Err(error) = LocalProjectIndexManager::handle(ctx)
|
||||
.update(ctx, |manager, ctx| {
|
||||
manager.index_directory(directory.clone(), ctx)
|
||||
})
|
||||
{
|
||||
log::warn!("Failed to start local project indexing: {error:#}");
|
||||
return;
|
||||
}
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(directory.clone(), ctx);
|
||||
});
|
||||
send_telemetry_from_ctx!(
|
||||
TelemetryEvent::AgentModeSetupCodebaseContextAction {
|
||||
action: AgentModeSetupCodebaseContextActionType::IndexCodebase,
|
||||
},
|
||||
ctx
|
||||
);
|
||||
CodebaseIndexManager::handle(ctx).update(ctx, |manager, ctx| {
|
||||
manager.index_directory(directory.clone(), ctx);
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.mark_step_running(InitStepKind::CodebaseContext, ctx);
|
||||
});
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
self.model.update(ctx, |model, ctx| {
|
||||
model.mark_step_completed(
|
||||
InitStepKind::CodebaseContext,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
use ai::index::full_source_code_embedding::manager::CodebaseIndexManager;
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
use ai::index::local_project_index::{
|
||||
LocalIndexStatus, LocalProjectIndexEvent, LocalProjectIndexManager,
|
||||
};
|
||||
use ai::project_context::model::ProjectContextModel;
|
||||
use enum_iterator::Sequence;
|
||||
use galaxy_util::local_or_remote_path::LocalOrRemotePath;
|
||||
@@ -10,7 +15,6 @@ use lsp::supported_servers::LSPServerType;
|
||||
use repo_metadata::repositories::DetectedRepositories;
|
||||
|
||||
use crate::ai::persisted_workspace::PersistedWorkspace;
|
||||
use crate::settings::CodeSettings;
|
||||
use crate::terminal::view::init_project::lsp_server_selector::LSPServerInfo;
|
||||
use crate::terminal::view::init_project::{
|
||||
CodebaseIndexingResult, CreateEnvironmentResult, InitActionResult, LanguageServersResult,
|
||||
@@ -124,7 +128,11 @@ impl InitProjectModel {
|
||||
path_env_var: Option<String>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) -> Self {
|
||||
let is_already_setup = !Self::should_have_available_steps(&pwd_path, ctx);
|
||||
#[cfg(feature = "local_fs")]
|
||||
let root_path = dunce::canonicalize(&pwd_path).unwrap_or(pwd_path);
|
||||
#[cfg(not(feature = "local_fs"))]
|
||||
let root_path = pwd_path;
|
||||
let is_already_setup = !Self::should_have_available_steps(&root_path, ctx);
|
||||
|
||||
Self {
|
||||
steps: [None, None, None, None, None],
|
||||
@@ -132,7 +140,7 @@ impl InitProjectModel {
|
||||
is_cancelled: false,
|
||||
is_already_setup,
|
||||
#[cfg(feature = "local_fs")]
|
||||
root_path: pwd_path,
|
||||
root_path,
|
||||
path_env_var,
|
||||
}
|
||||
}
|
||||
@@ -154,6 +162,17 @@ impl InitProjectModel {
|
||||
);
|
||||
|
||||
// Start async computations for subsequent steps
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
{
|
||||
let local_index_manager = LocalProjectIndexManager::handle(ctx);
|
||||
ctx.subscribe_to_model(&local_index_manager, |model, _, event, ctx| {
|
||||
if let LocalProjectIndexEvent::StatusChanged { root_path, status } = event {
|
||||
if root_path == &model.root_path {
|
||||
model.handle_local_index_status(status, ctx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
self.compute_codebase_context_step(&pwd_path, ctx);
|
||||
if self.path_env_var.is_some() {
|
||||
self.compute_language_servers_step(&pwd_path, ctx);
|
||||
@@ -176,14 +195,12 @@ impl InitProjectModel {
|
||||
|
||||
/// Check if there are any steps that need user action
|
||||
pub fn should_have_available_steps(path: &Path, ctx: &galaxyui::AppContext) -> bool {
|
||||
// Note that we consider auto-indexing setting to true to satisfy the codebase context step.
|
||||
// This avoids the potential race condition with the banner showing just when we start auto-indexing.
|
||||
// /init is an explicit user request, so its indexing step must remain available even
|
||||
// when background auto-indexing is enabled. The auto-index setting only controls whether
|
||||
// navigation/startup starts indexing without this explicit action.
|
||||
let has_pending_codebase_context = UserWorkspaces::as_ref(ctx)
|
||||
.is_codebase_context_enabled(ctx)
|
||||
&& CodebaseIndexManager::as_ref(ctx)
|
||||
.get_codebase_index_status_for_path(path, ctx)
|
||||
.is_none()
|
||||
&& !*CodeSettings::as_ref(ctx).auto_indexing_enabled;
|
||||
&& !Self::codebase_index_ready(path, ctx);
|
||||
|
||||
let has_pending_project_scoped_rules = ProjectContextModel::as_ref(ctx)
|
||||
.find_applicable_project_rules(&LocalOrRemotePath::Local(path.to_path_buf()))
|
||||
@@ -192,6 +209,18 @@ impl InitProjectModel {
|
||||
has_pending_codebase_context || has_pending_project_scoped_rules
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
fn codebase_index_ready(path: &Path, ctx: &galaxyui::AppContext) -> bool {
|
||||
LocalProjectIndexManager::as_ref(ctx).is_ready_for_path(path)
|
||||
}
|
||||
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
fn codebase_index_ready(path: &Path, ctx: &galaxyui::AppContext) -> bool {
|
||||
CodebaseIndexManager::as_ref(ctx)
|
||||
.get_codebase_index_status_for_path(path, ctx)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn get_step(&self, kind: InitStepKind) -> Option<&InitStep> {
|
||||
self.steps
|
||||
.get(kind as usize)
|
||||
@@ -368,9 +397,12 @@ impl InitProjectModel {
|
||||
return;
|
||||
}
|
||||
|
||||
let codebase_index_manager = CodebaseIndexManager::handle(ctx);
|
||||
let is_indexed = codebase_index_manager
|
||||
.as_ref(ctx)
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
let is_indexed = LocalProjectIndexManager::as_ref(ctx)
|
||||
.status_for_path(pwd_path)
|
||||
.is_some_and(|(_, status)| matches!(status, LocalIndexStatus::Ready { .. }));
|
||||
#[cfg(any(not(feature = "local_fs"), target_family = "wasm"))]
|
||||
let is_indexed = CodebaseIndexManager::as_ref(ctx)
|
||||
.get_codebase_index_status_for_path(pwd_path, ctx)
|
||||
.is_some();
|
||||
|
||||
@@ -397,6 +429,47 @@ impl InitProjectModel {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "local_fs", not(target_family = "wasm")))]
|
||||
fn handle_local_index_status(
|
||||
&mut self,
|
||||
status: &LocalIndexStatus,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if !matches!(
|
||||
self.get_step(InitStepKind::CodebaseContext)
|
||||
.map(|step| &step.status),
|
||||
Some(InitStepStatus::Running)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let root_path = self.root_path.clone();
|
||||
let Some(step) = self.get_step_mut(InitStepKind::CodebaseContext) else {
|
||||
return;
|
||||
};
|
||||
match status {
|
||||
LocalIndexStatus::Indexing => {
|
||||
step.status = InitStepStatus::Running;
|
||||
ctx.notify();
|
||||
}
|
||||
LocalIndexStatus::Ready { .. } => {
|
||||
step.status = InitStepStatus::Completed(InitActionResult::CodebaseContext(
|
||||
CodebaseIndexingResult::Accepted,
|
||||
));
|
||||
ctx.emit(InitProjectModelEvent::StepCompleted(
|
||||
InitStepKind::CodebaseContext,
|
||||
));
|
||||
self.maybe_emit_next_step(ctx);
|
||||
}
|
||||
LocalIndexStatus::Failed { message } => {
|
||||
step.status = InitStepStatus::Ready(InitStepData::CodebaseContext {
|
||||
pwd_path: root_path,
|
||||
});
|
||||
ctx.notify();
|
||||
ctx.emit(InitProjectModelEvent::CodebaseIndexFailed(message.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_language_servers_step(&mut self, pwd_path: &Path, ctx: &mut ModelContext<Self>) {
|
||||
// Start as Pending
|
||||
self.set_step(
|
||||
@@ -584,6 +657,8 @@ pub enum InitProjectModelEvent {
|
||||
InitCompleted,
|
||||
/// Trigger AGENTS.md generation slash command
|
||||
GenerateProjectRules,
|
||||
/// The local index failed and can be retried from the same step.
|
||||
CodebaseIndexFailed(String),
|
||||
/// Trigger AGENTS.md regeneration
|
||||
RegenerateProjectRules,
|
||||
/// View codebase context status
|
||||
|
||||
Reference in New Issue
Block a user