From 1c6377c98fb6e43adc2c737d73205e099ea85fa9 Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Wed, 13 Aug 2025 14:00:17 -0700 Subject: [PATCH 1/2] remove and cleanup unused code --- crates/goose-cli/src/cli.rs | 26 - crates/goose-cli/src/commands/mod.rs | 1 - crates/goose-cli/src/commands/project.rs | 304 ----------- crates/goose-cli/src/lib.rs | 1 - crates/goose-cli/src/project_tracker.rs | 146 ----- crates/goose-cli/src/session/mod.rs | 34 -- crates/goose-server/src/routes/mod.rs | 2 - crates/goose-server/src/routes/project.rs | 358 ------------- crates/goose/src/context_mgmt/auto_compact.rs | 2 - crates/goose/src/lib.rs | 1 - crates/goose/src/project/mod.rs | 68 --- crates/goose/src/project/storage.rs | 239 --------- crates/goose/src/scheduler.rs | 1 - crates/goose/src/session/storage.rs | 6 +- crates/goose/tests/test_support.rs | 1 - ui/desktop/openapi.json | 5 - ui/desktop/scripts/copy-windows-dlls.js | 49 -- ui/desktop/scripts/generate-update-icon.js | 53 -- ui/desktop/scripts/prepare-platform.js | 16 - ui/desktop/src/App.tsx | 61 --- ui/desktop/src/api/types.gen.ts | 4 - ui/desktop/src/components/ApiKeyWarning.tsx | 91 ---- .../src/components/LoadingPlaceholder.tsx | 16 - ui/desktop/src/components/Modal.tsx | 83 --- ui/desktop/src/components/SplashPills.tsx | 84 --- ui/desktop/src/components/cli/CLIChatView.tsx | 17 - ui/desktop/src/components/cli/CLIHub.tsx | 128 ----- .../src/components/common/ActivityHeatmap.tsx | 276 ---------- ui/desktop/src/components/icons/Bars.tsx | 9 - .../projects/AddSessionToProjectModal.tsx | 146 ----- .../projects/CreateProjectModal.tsx | 151 ------ .../src/components/projects/ProjectCard.tsx | 47 -- .../projects/ProjectDetailsView.tsx | 498 ------------------ .../components/projects/ProjectsContainer.tsx | 33 -- .../src/components/projects/ProjectsView.tsx | 214 -------- .../projects/UpdateProjectModal.tsx | 181 ------- .../components/sessions/SessionsInsights.tsx | 77 --- .../models/model_list/BaseModelsList.tsx | 170 ------ .../models/model_list/recentModels.ts | 30 -- .../providers/interfaces/ButtonCallbacks.tsx | 6 - .../interfaces/ConfigurationAction.tsx | 7 - .../providers/interfaces/OllamaMetadata.tsx | 3 - .../subcomponents/ProviderSetupHeader.tsx | 28 - .../providers/parameters/UpdateSecrets.tsx | 0 .../providers/state/providerState.tsx | 0 .../providers/subcomponents/CardActions.tsx | 18 - .../settings/recipes/ViewRecipe.tsx | 3 - ui/desktop/src/components/ui/Box.tsx | 33 -- .../src/components/ui/DeepLinkModal.tsx | 243 --------- ui/desktop/src/components/ui/Send.tsx | 18 - ui/desktop/src/components/ui/VertDots.tsx | 25 - ui/desktop/src/components/ui/X.tsx | 20 - ui/desktop/src/components/ui/alert-dialog.tsx | 135 ----- ui/desktop/src/components/ui/avatar.tsx | 45 -- ui/desktop/src/components/ui/badge.tsx | 33 -- ui/desktop/src/components/ui/checkbox.tsx | 29 - ui/desktop/src/components/ui/label.tsx | 21 - ui/desktop/src/components/ui/modal.tsx | 98 ---- ui/desktop/src/components/ui/popover.tsx | 36 -- ui/desktop/src/components/ui/select-styles.ts | 84 --- ui/desktop/src/components/ui/textarea.tsx | 18 - .../src/components/ui/vertical-tabs.tsx | 68 --- ui/desktop/src/env_vars.ts | 2 - ui/desktop/src/flags.ts | 0 ui/desktop/src/floating-button-script.js | 28 - ui/desktop/src/hooks/useDarkMode.ts | 42 -- ui/desktop/src/projects.ts | 225 -------- ui/desktop/src/setup-events.ts | 71 --- ui/desktop/src/types/electron.d.ts | 14 - ui/desktop/src/types/index.ts | 1 - ui/desktop/src/types/project.ts | 73 --- ui/desktop/src/utils/session.ts | 0 ui/desktop/test-extension-dialog.js | 10 - 73 files changed, 1 insertion(+), 5065 deletions(-) delete mode 100644 crates/goose-cli/src/commands/project.rs delete mode 100644 crates/goose-cli/src/project_tracker.rs delete mode 100644 crates/goose-server/src/routes/project.rs delete mode 100644 crates/goose/src/project/mod.rs delete mode 100644 crates/goose/src/project/storage.rs delete mode 100644 ui/desktop/scripts/copy-windows-dlls.js delete mode 100644 ui/desktop/scripts/generate-update-icon.js delete mode 100644 ui/desktop/scripts/prepare-platform.js delete mode 100644 ui/desktop/src/components/ApiKeyWarning.tsx delete mode 100644 ui/desktop/src/components/LoadingPlaceholder.tsx delete mode 100644 ui/desktop/src/components/Modal.tsx delete mode 100644 ui/desktop/src/components/SplashPills.tsx delete mode 100644 ui/desktop/src/components/cli/CLIChatView.tsx delete mode 100644 ui/desktop/src/components/cli/CLIHub.tsx delete mode 100644 ui/desktop/src/components/common/ActivityHeatmap.tsx delete mode 100644 ui/desktop/src/components/icons/Bars.tsx delete mode 100644 ui/desktop/src/components/projects/AddSessionToProjectModal.tsx delete mode 100644 ui/desktop/src/components/projects/CreateProjectModal.tsx delete mode 100644 ui/desktop/src/components/projects/ProjectCard.tsx delete mode 100644 ui/desktop/src/components/projects/ProjectDetailsView.tsx delete mode 100644 ui/desktop/src/components/projects/ProjectsContainer.tsx delete mode 100644 ui/desktop/src/components/projects/ProjectsView.tsx delete mode 100644 ui/desktop/src/components/projects/UpdateProjectModal.tsx delete mode 100644 ui/desktop/src/components/settings/models/model_list/BaseModelsList.tsx delete mode 100644 ui/desktop/src/components/settings/models/model_list/recentModels.ts delete mode 100644 ui/desktop/src/components/settings/providers/interfaces/ButtonCallbacks.tsx delete mode 100644 ui/desktop/src/components/settings/providers/interfaces/ConfigurationAction.tsx delete mode 100644 ui/desktop/src/components/settings/providers/interfaces/OllamaMetadata.tsx delete mode 100644 ui/desktop/src/components/settings/providers/modal/subcomponents/ProviderSetupHeader.tsx delete mode 100644 ui/desktop/src/components/settings/providers/parameters/UpdateSecrets.tsx delete mode 100644 ui/desktop/src/components/settings/providers/state/providerState.tsx delete mode 100644 ui/desktop/src/components/settings/providers/subcomponents/CardActions.tsx delete mode 100644 ui/desktop/src/components/settings/recipes/ViewRecipe.tsx delete mode 100644 ui/desktop/src/components/ui/Box.tsx delete mode 100644 ui/desktop/src/components/ui/DeepLinkModal.tsx delete mode 100644 ui/desktop/src/components/ui/Send.tsx delete mode 100644 ui/desktop/src/components/ui/VertDots.tsx delete mode 100644 ui/desktop/src/components/ui/X.tsx delete mode 100644 ui/desktop/src/components/ui/alert-dialog.tsx delete mode 100644 ui/desktop/src/components/ui/avatar.tsx delete mode 100644 ui/desktop/src/components/ui/badge.tsx delete mode 100644 ui/desktop/src/components/ui/checkbox.tsx delete mode 100644 ui/desktop/src/components/ui/label.tsx delete mode 100644 ui/desktop/src/components/ui/modal.tsx delete mode 100644 ui/desktop/src/components/ui/popover.tsx delete mode 100644 ui/desktop/src/components/ui/select-styles.ts delete mode 100644 ui/desktop/src/components/ui/textarea.tsx delete mode 100644 ui/desktop/src/components/ui/vertical-tabs.tsx delete mode 100644 ui/desktop/src/env_vars.ts delete mode 100644 ui/desktop/src/flags.ts delete mode 100644 ui/desktop/src/floating-button-script.js delete mode 100644 ui/desktop/src/hooks/useDarkMode.ts delete mode 100644 ui/desktop/src/projects.ts delete mode 100644 ui/desktop/src/setup-events.ts delete mode 100644 ui/desktop/src/types/electron.d.ts delete mode 100644 ui/desktop/src/types/index.ts delete mode 100644 ui/desktop/src/types/project.ts delete mode 100644 ui/desktop/src/utils/session.ts delete mode 100644 ui/desktop/test-extension-dialog.js diff --git a/crates/goose-cli/src/cli.rs b/crates/goose-cli/src/cli.rs index 76edd308dd8d..ad0bd828ef69 100644 --- a/crates/goose-cli/src/cli.rs +++ b/crates/goose-cli/src/cli.rs @@ -7,7 +7,6 @@ use crate::commands::bench::agent_generator; use crate::commands::configure::handle_configure; use crate::commands::info::handle_info; use crate::commands::mcp::run_server; -use crate::commands::project::{handle_project_default, handle_projects_interactive}; use crate::commands::recipe::{handle_deeplink, handle_list, handle_validate}; // Import the new handlers from commands::schedule use crate::commands::schedule::{ @@ -387,14 +386,6 @@ enum Command { builtins: Vec, }, - /// Open the last project directory - #[command(about = "Open the last project directory", visible_alias = "p")] - Project {}, - - /// List recent project directories - #[command(about = "List recent project directories", visible_alias = "ps")] - Projects, - /// Execute commands from an instruction file #[command(about = "Execute commands from an instruction file or stdin")] Run { @@ -700,18 +691,11 @@ pub struct RecipeInfo { pub async fn cli() -> Result<()> { let cli = Cli::parse(); - // Track the current directory in projects.json - if let Err(e) = crate::project_tracker::update_project_tracker(None, None) { - eprintln!("Warning: Failed to update project tracker: {}", e); - } - let command_name = match &cli.command { Some(Command::Configure {}) => "configure", Some(Command::Info { .. }) => "info", Some(Command::Mcp { .. }) => "mcp", Some(Command::Session { .. }) => "session", - Some(Command::Project {}) => "project", - Some(Command::Projects) => "projects", Some(Command::Run { .. }) => "run", Some(Command::Schedule { .. }) => "schedule", Some(Command::Update { .. }) => "update", @@ -862,16 +846,6 @@ pub async fn cli() -> Result<()> { } }; } - Some(Command::Project {}) => { - // Default behavior: offer to resume the last project - handle_project_default()?; - return Ok(()); - } - Some(Command::Projects) => { - // Interactive project selection - handle_projects_interactive()?; - return Ok(()); - } Some(Command::Run { instructions, diff --git a/crates/goose-cli/src/commands/mod.rs b/crates/goose-cli/src/commands/mod.rs index 72ce9be243ed..a52216d01b5b 100644 --- a/crates/goose-cli/src/commands/mod.rs +++ b/crates/goose-cli/src/commands/mod.rs @@ -2,7 +2,6 @@ pub mod bench; pub mod configure; pub mod info; pub mod mcp; -pub mod project; pub mod recipe; pub mod schedule; pub mod session; diff --git a/crates/goose-cli/src/commands/project.rs b/crates/goose-cli/src/commands/project.rs deleted file mode 100644 index e049e66e323e..000000000000 --- a/crates/goose-cli/src/commands/project.rs +++ /dev/null @@ -1,304 +0,0 @@ -use anyhow::Result; -use chrono::DateTime; -use cliclack::{self, intro, outro}; -use std::path::Path; - -use crate::project_tracker::ProjectTracker; -use goose::utils::safe_truncate; - -/// Format a DateTime for display -fn format_date(date: DateTime) -> String { - // Format: "2025-05-08 18:15:30" - date.format("%Y-%m-%d %H:%M:%S").to_string() -} - -/// Handle the default project command -/// -/// Offers options to resume the most recently accessed project -pub fn handle_project_default() -> Result<()> { - let tracker = ProjectTracker::load()?; - let mut projects = tracker.list_projects(); - - if projects.is_empty() { - // If no projects exist, just start a new one in the current directory - println!("No previous projects found. Starting a new session in the current directory."); - let mut command = std::process::Command::new("goose"); - command.arg("session"); - let status = command.status()?; - - if !status.success() { - println!("Failed to run Goose. Exit code: {:?}", status.code()); - } - return Ok(()); - } - - // Sort projects by last_accessed (newest first) - projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed)); - - // Get the most recent project - let project = &projects[0]; - let project_dir = &project.path; - - // Check if the directory exists - if !Path::new(project_dir).exists() { - println!( - "Most recent project directory '{}' no longer exists.", - project_dir - ); - return Ok(()); - } - - // Format the path for display - let path = Path::new(project_dir); - let components: Vec<_> = path.components().collect(); - let len = components.len(); - let short_path = if len <= 2 { - project_dir.clone() - } else { - let mut path_str = String::new(); - path_str.push_str("..."); - for component in components.iter().skip(len - 2) { - path_str.push('/'); - path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); - } - path_str - }; - - // Ask the user what they want to do - let _ = intro("Goose Project Manager"); - - let current_dir = std::env::current_dir()?; - let current_dir_display = current_dir.display(); - - let choice = cliclack::select("Choose an option:") - .item( - "resume", - format!("Resume project with session: {}", short_path), - "Continue with the previous session", - ) - .item( - "fresh", - format!("Resume project with fresh session: {}", short_path), - "Change to the project directory but start a new session", - ) - .item( - "new", - format!( - "Start new project in current directory: {}", - current_dir_display - ), - "Stay in the current directory and start a new session", - ) - .interact()?; - - match choice { - "resume" => { - let _ = outro(format!("Changing to directory: {}", project_dir)); - - // Get the session ID if available - let session_id = project.last_session_id.clone(); - - // Change to the project directory - std::env::set_current_dir(project_dir)?; - - // Build the command to run Goose - let mut command = std::process::Command::new("goose"); - command.arg("session"); - - if let Some(id) = session_id { - command.arg("--name").arg(&id).arg("--resume"); - println!("Resuming session: {}", id); - } - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run Goose. Exit code: {:?}", status.code()); - } - } - "fresh" => { - let _ = outro(format!( - "Changing to directory: {} with a fresh session", - project_dir - )); - - // Change to the project directory - std::env::set_current_dir(project_dir)?; - - // Build the command to run Goose with a fresh session - let mut command = std::process::Command::new("goose"); - command.arg("session"); - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run Goose. Exit code: {:?}", status.code()); - } - } - "new" => { - let _ = outro("Starting a new session in the current directory"); - - // Build the command to run Goose - let mut command = std::process::Command::new("goose"); - command.arg("session"); - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run Goose. Exit code: {:?}", status.code()); - } - } - _ => { - let _ = outro("Operation canceled"); - } - } - - Ok(()) -} - -/// Handle the interactive projects command -/// -/// Shows a list of projects and lets the user select one to resume -pub fn handle_projects_interactive() -> Result<()> { - let tracker = ProjectTracker::load()?; - let mut projects = tracker.list_projects(); - - if projects.is_empty() { - println!("No projects found."); - return Ok(()); - } - - // Sort projects by last_accessed (newest first) - projects.sort_by(|a, b| b.last_accessed.cmp(&a.last_accessed)); - - // Format project paths for display - let project_choices: Vec<(String, String)> = projects - .iter() - .enumerate() - .map(|(i, project)| { - let path = Path::new(&project.path); - let components: Vec<_> = path.components().collect(); - let len = components.len(); - let short_path = if len <= 2 { - project.path.clone() - } else { - let mut path_str = String::new(); - path_str.push_str("..."); - for component in components.iter().skip(len - 2) { - path_str.push('/'); - path_str.push_str(component.as_os_str().to_string_lossy().as_ref()); - } - path_str - }; - - // Include last instruction if available (truncated) - let instruction_preview = - project - .last_instruction - .as_ref() - .map_or(String::new(), |instr| { - let truncated = safe_truncate(instr, 40); - format!(" [{}]", truncated) - }); - - let formatted_date = format_date(project.last_accessed); - ( - format!("{}", i + 1), // Value to return - format!("{} ({}){}", short_path, formatted_date, instruction_preview), // Display text with instruction - ) - }) - .collect(); - - // Let the user select a project - let _ = intro("Goose Project Manager"); - let mut select = cliclack::select("Select a project:"); - - // Add each project as an option - for (value, display) in &project_choices { - select = select.item(value, display, ""); - } - - // Add a cancel option - let cancel_value = String::from("cancel"); - select = select.item(&cancel_value, "Cancel", "Don't resume any project"); - - let selected = select.interact()?; - - if selected == "cancel" { - let _ = outro("Project selection canceled."); - return Ok(()); - } - - // Parse the selected index - let index = selected.parse::().unwrap_or(0); - if index == 0 || index > projects.len() { - let _ = outro("Invalid selection."); - return Ok(()); - } - - // Get the selected project - let project = &projects[index - 1]; - let project_dir = &project.path; - - // Check if the directory exists - if !Path::new(project_dir).exists() { - let _ = outro(format!( - "Project directory '{}' no longer exists.", - project_dir - )); - return Ok(()); - } - - // Ask if the user wants to resume the session or start a new one - let session_id = project.last_session_id.clone(); - let has_previous_session = session_id.is_some(); - - // Change to the project directory first - std::env::set_current_dir(project_dir)?; - let _ = outro(format!("Changed to directory: {}", project_dir)); - - // Only ask about resuming if there's a previous session - let resume_session = if has_previous_session { - let session_choice = cliclack::select("What would you like to do?") - .item( - "resume", - "Resume previous session", - "Continue with the previous session", - ) - .item( - "new", - "Start new session", - "Start a fresh session in this project directory", - ) - .interact()?; - - session_choice == "resume" - } else { - false - }; - - // Build the command to run Goose - let mut command = std::process::Command::new("goose"); - command.arg("session"); - - if resume_session { - if let Some(id) = session_id { - command.arg("--name").arg(&id).arg("--resume"); - println!("Resuming session: {}", id); - } - } else { - println!("Starting new session"); - } - - // Execute the command - let status = command.status()?; - - if !status.success() { - println!("Failed to run Goose. Exit code: {:?}", status.code()); - } - - Ok(()) -} diff --git a/crates/goose-cli/src/lib.rs b/crates/goose-cli/src/lib.rs index ad0641b1817b..f2e6a3591b63 100644 --- a/crates/goose-cli/src/lib.rs +++ b/crates/goose-cli/src/lib.rs @@ -3,7 +3,6 @@ use once_cell::sync::Lazy; pub mod cli; pub mod commands; pub mod logging; -pub mod project_tracker; pub mod recipes; pub mod scenario_tests; pub mod session; diff --git a/crates/goose-cli/src/project_tracker.rs b/crates/goose-cli/src/project_tracker.rs deleted file mode 100644 index 1b11bbf33631..000000000000 --- a/crates/goose-cli/src/project_tracker.rs +++ /dev/null @@ -1,146 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Utc}; -use etcetera::{choose_app_strategy, AppStrategy}; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; -use std::fs; -use std::path::{Path, PathBuf}; - -/// Structure to track project information -#[derive(Debug, Serialize, Deserialize)] -pub struct ProjectInfo { - /// The absolute path to the project directory - pub path: String, - /// Last time the project was accessed - pub last_accessed: DateTime, - /// Last instruction sent to goose (if available) - pub last_instruction: Option, - /// Last session ID associated with this project - pub last_session_id: Option, -} - -/// Structure to hold all tracked projects -#[derive(Debug, Serialize, Deserialize)] -pub struct ProjectTracker { - projects: HashMap, -} - -/// Project information with path as a separate field for easier access -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct ProjectInfoDisplay { - /// The absolute path to the project directory - pub path: String, - /// Last time the project was accessed - pub last_accessed: DateTime, - /// Last instruction sent to goose (if available) - pub last_instruction: Option, - /// Last session ID associated with this project - pub last_session_id: Option, -} - -impl ProjectTracker { - /// Get the path to the projects.json file - fn get_projects_file() -> Result { - let projects_file = choose_app_strategy(crate::APP_STRATEGY.clone()) - .context("goose requires a home dir")? - .in_data_dir("projects.json"); - - // Ensure data directory exists - if let Some(parent) = projects_file.parent() { - if !parent.exists() { - fs::create_dir_all(parent)?; - } - } - - Ok(projects_file) - } - - /// Load the project tracker from the projects.json file - pub fn load() -> Result { - let projects_file = Self::get_projects_file()?; - - if projects_file.exists() { - let file_content = fs::read_to_string(&projects_file)?; - let tracker: ProjectTracker = serde_json::from_str(&file_content) - .context("Failed to parse projects.json file")?; - Ok(tracker) - } else { - // If the file doesn't exist, create a new empty tracker - Ok(ProjectTracker { - projects: HashMap::new(), - }) - } - } - - /// Save the project tracker to the projects.json file - pub fn save(&self) -> Result<()> { - let projects_file = Self::get_projects_file()?; - let json = serde_json::to_string_pretty(self)?; - fs::write(projects_file, json)?; - Ok(()) - } - - /// Update project information for the current directory - /// - /// # Arguments - /// * `project_dir` - The project directory to update - /// * `instruction` - Optional instruction that was sent to goose - /// * `session_id` - Optional session ID associated with this project - pub fn update_project( - &mut self, - project_dir: &Path, - instruction: Option<&str>, - session_id: Option<&str>, - ) -> Result<()> { - let dir_str = project_dir.to_string_lossy().to_string(); - - // Create or update the project entry - let project_info = self.projects.entry(dir_str.clone()).or_insert(ProjectInfo { - path: dir_str, - last_accessed: Utc::now(), - last_instruction: None, - last_session_id: None, - }); - - // Update the last accessed time - project_info.last_accessed = Utc::now(); - - // Update the last instruction if provided - if let Some(instr) = instruction { - project_info.last_instruction = Some(instr.to_string()); - } - - // Update the session ID if provided - if let Some(id) = session_id { - project_info.last_session_id = Some(id.to_string()); - } - - self.save() - } - - /// List all tracked projects - /// - /// Returns a vector of ProjectInfoDisplay objects - pub fn list_projects(&self) -> Vec { - self.projects - .values() - .map(|info| ProjectInfoDisplay { - path: info.path.clone(), - last_accessed: info.last_accessed, - last_instruction: info.last_instruction.clone(), - last_session_id: info.last_session_id.clone(), - }) - .collect() - } -} - -/// Update the project tracker with the current directory and optional instruction -/// -/// # Arguments -/// * `instruction` - Optional instruction that was sent to goose -/// * `session_id` - Optional session ID associated with this project -pub fn update_project_tracker(instruction: Option<&str>, session_id: Option<&str>) -> Result<()> { - let current_dir = std::env::current_dir()?; - let mut tracker = ProjectTracker::load()?; - tracker.update_project(¤t_dir, instruction, session_id) -} diff --git a/crates/goose-cli/src/session/mod.rs b/crates/goose-cli/src/session/mod.rs index 5b9b75f7f922..80e41eafcc58 100644 --- a/crates/goose-cli/src/session/mod.rs +++ b/crates/goose-cli/src/session/mod.rs @@ -370,7 +370,6 @@ impl Session { cancel_token: CancellationToken, ) -> Result<()> { let cancel_token = cancel_token.clone(); - let message_text = message.as_concat_text(); self.push_message(message); // Get the provider from the agent for description generation @@ -392,24 +391,6 @@ impl Session { .await?; } - // Track the current directory and last instruction in projects.json - let session_id = self - .session_file - .as_ref() - .and_then(|p| p.file_stem()) - .and_then(|s| s.to_str()) - .map(|s| s.to_string()); - - if let Err(e) = crate::project_tracker::update_project_tracker( - Some(&message_text), - session_id.as_deref(), - ) { - eprintln!( - "Warning: Failed to update project tracker with instruction: {}", - e - ); - } - self.process_agent_response(false, cancel_token).await?; Ok(()) } @@ -488,21 +469,6 @@ impl Session { self.push_message(Message::user().with_text(&content)); - // Track the current directory and last instruction in projects.json - let session_id = self - .session_file - .as_ref() - .and_then(|p| p.file_stem()) - .and_then(|s| s.to_str()) - .map(|s| s.to_string()); - - if let Err(e) = crate::project_tracker::update_project_tracker( - Some(&content), - session_id.as_deref(), - ) { - eprintln!("Warning: Failed to update project tracker with instruction: {}", e); - } - let provider = self.agent.provider().await?; // Persist messages with provider for automatic description generation diff --git a/crates/goose-server/src/routes/mod.rs b/crates/goose-server/src/routes/mod.rs index 0c14880bde0b..dfc96b8c1bbb 100644 --- a/crates/goose-server/src/routes/mod.rs +++ b/crates/goose-server/src/routes/mod.rs @@ -5,7 +5,6 @@ pub mod config_management; pub mod context; pub mod extension; pub mod health; -pub mod project; pub mod recipe; pub mod reply; pub mod schedule; @@ -29,6 +28,5 @@ pub fn configure(state: Arc) -> Router { .merge(recipe::routes(state.clone())) .merge(session::routes(state.clone())) .merge(schedule::routes(state.clone())) - .merge(project::routes(state.clone())) .merge(setup::routes(state.clone())) } diff --git a/crates/goose-server/src/routes/project.rs b/crates/goose-server/src/routes/project.rs deleted file mode 100644 index a83c1a0101e4..000000000000 --- a/crates/goose-server/src/routes/project.rs +++ /dev/null @@ -1,358 +0,0 @@ -use super::utils::verify_secret_key; -use std::sync::Arc; - -use crate::state::AppState; -use axum::{ - extract::{Path, State}, - http::{HeaderMap, StatusCode}, - routing::{delete, get, post, put}, - Json, Router, -}; -use goose::project::{Project, ProjectMetadata}; -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; - -#[derive(Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct CreateProjectRequest { - /// Display name of the project - pub name: String, - /// Optional description of the project - pub description: Option, - /// Default working directory for sessions in this project - #[schema(value_type = String)] - pub default_directory: std::path::PathBuf, -} - -#[derive(Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct UpdateProjectRequest { - /// Display name of the project - pub name: Option, - /// Optional description of the project - pub description: Option>, - /// Default working directory for sessions in this project - #[schema(value_type = String)] - pub default_directory: Option, -} - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ProjectListResponse { - /// List of available project metadata objects - pub projects: Vec, -} - -#[derive(Serialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ProjectResponse { - /// Project details - pub project: Project, -} - -#[utoipa::path( - get, - path = "/projects", - responses( - (status = 200, description = "List of available projects retrieved successfully", body = ProjectListResponse), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// List all available projects -async fn list_projects( - State(state): State>, - headers: HeaderMap, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - let projects = - goose::project::list_projects().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - Ok(Json(ProjectListResponse { projects })) -} - -#[utoipa::path( - get, - path = "/projects/{project_id}", - params( - ("project_id" = String, Path, description = "Unique identifier for the project") - ), - responses( - (status = 200, description = "Project details retrieved successfully", body = ProjectResponse), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 404, description = "Project not found"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Get a specific project details -async fn get_project_details( - State(state): State>, - headers: HeaderMap, - Path(project_id): Path, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - let project = goose::project::get_project(&project_id).map_err(|e| { - if e.to_string().contains("not found") { - StatusCode::NOT_FOUND - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - })?; - - Ok(Json(ProjectResponse { project })) -} - -#[utoipa::path( - post, - path = "/projects", - request_body = CreateProjectRequest, - responses( - (status = 201, description = "Project created successfully", body = ProjectResponse), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 400, description = "Invalid request - Bad input parameters"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Create a new project -async fn create_project( - State(state): State>, - headers: HeaderMap, - Json(create_req): Json, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - // Validate input - if create_req.name.trim().is_empty() { - return Err(StatusCode::BAD_REQUEST); - } - - let project = goose::project::create_project( - create_req.name, - create_req.description, - create_req.default_directory, - ) - .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; - - Ok(Json(ProjectResponse { project })) -} - -#[utoipa::path( - put, - path = "/projects/{project_id}", - params( - ("project_id" = String, Path, description = "Unique identifier for the project") - ), - request_body = UpdateProjectRequest, - responses( - (status = 200, description = "Project updated successfully", body = ProjectResponse), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 404, description = "Project not found"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Update a project -async fn update_project( - State(state): State>, - headers: HeaderMap, - Path(project_id): Path, - Json(update_req): Json, -) -> Result, StatusCode> { - verify_secret_key(&headers, &state)?; - - let project = goose::project::update_project( - &project_id, - update_req.name, - update_req.description, - update_req.default_directory, - ) - .map_err(|e| { - if e.to_string().contains("not found") { - StatusCode::NOT_FOUND - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - })?; - - Ok(Json(ProjectResponse { project })) -} - -#[utoipa::path( - delete, - path = "/projects/{project_id}", - params( - ("project_id" = String, Path, description = "Unique identifier for the project") - ), - responses( - (status = 204, description = "Project deleted successfully"), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 404, description = "Project not found"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Delete a project -async fn delete_project( - State(state): State>, - headers: HeaderMap, - Path(project_id): Path, -) -> Result { - verify_secret_key(&headers, &state)?; - - goose::project::delete_project(&project_id).map_err(|e| { - if e.to_string().contains("not found") { - StatusCode::NOT_FOUND - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - })?; - - Ok(StatusCode::NO_CONTENT) -} - -#[utoipa::path( - post, - path = "/projects/{project_id}/sessions/{session_id}", - params( - ("project_id" = String, Path, description = "Unique identifier for the project"), - ("session_id" = String, Path, description = "Unique identifier for the session to add") - ), - responses( - (status = 204, description = "Session added to project successfully"), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 404, description = "Project or session not found"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Add session to project -async fn add_session_to_project( - State(state): State>, - headers: HeaderMap, - Path((project_id, session_id)): Path<(String, String)>, -) -> Result { - verify_secret_key(&headers, &state)?; - - // Add the session to project - goose::project::add_session_to_project(&project_id, &session_id).map_err(|e| { - if e.to_string().contains("not found") { - StatusCode::NOT_FOUND - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - })?; - - // Also update session metadata to include the project_id - let session_path = - goose::session::get_path(goose::session::Identifier::Name(session_id.clone())) - .map_err(|_| StatusCode::NOT_FOUND)?; - let mut metadata = - goose::session::read_metadata(&session_path).map_err(|_| StatusCode::NOT_FOUND)?; - metadata.project_id = Some(project_id); - - tokio::task::spawn(async move { - if let Err(e) = goose::session::update_metadata(&session_path, &metadata).await { - tracing::error!("Failed to update session metadata: {}", e); - } - }); - - Ok(StatusCode::NO_CONTENT) -} - -#[utoipa::path( - delete, - path = "/projects/{project_id}/sessions/{session_id}", - params( - ("project_id" = String, Path, description = "Unique identifier for the project"), - ("session_id" = String, Path, description = "Unique identifier for the session to remove") - ), - responses( - (status = 204, description = "Session removed from project successfully"), - (status = 401, description = "Unauthorized - Invalid or missing API key"), - (status = 404, description = "Project or session not found"), - (status = 500, description = "Internal server error") - ), - security( - ("api_key" = []) - ), - tag = "Project Management" -)] -// Remove session from project -async fn remove_session_from_project( - State(state): State>, - headers: HeaderMap, - Path((project_id, session_id)): Path<(String, String)>, -) -> Result { - verify_secret_key(&headers, &state)?; - - // Remove from project - goose::project::remove_session_from_project(&project_id, &session_id).map_err(|e| { - if e.to_string().contains("not found") { - StatusCode::NOT_FOUND - } else { - StatusCode::INTERNAL_SERVER_ERROR - } - })?; - - // Also update session metadata to remove the project_id - let session_path = - goose::session::get_path(goose::session::Identifier::Name(session_id.clone())) - .map_err(|_| StatusCode::NOT_FOUND)?; - let mut metadata = - goose::session::read_metadata(&session_path).map_err(|_| StatusCode::NOT_FOUND)?; - - // Only update if this session was actually in this project - if metadata.project_id.as_deref() == Some(&project_id) { - metadata.project_id = None; - - tokio::task::spawn(async move { - if let Err(e) = goose::session::update_metadata(&session_path, &metadata).await { - tracing::error!("Failed to update session metadata: {}", e); - } - }); - } - - Ok(StatusCode::NO_CONTENT) -} - -// Configure routes for this module -pub fn routes(state: Arc) -> Router { - Router::new() - .route("/projects", get(list_projects)) - .route("/projects", post(create_project)) - .route("/projects/{project_id}", get(get_project_details)) - .route("/projects/{project_id}", put(update_project)) - .route("/projects/{project_id}", delete(delete_project)) - .route( - "/projects/{project_id}/sessions/{session_id}", - post(add_session_to_project), - ) - .route( - "/projects/{project_id}/sessions/{session_id}", - delete(remove_session_from_project), - ) - .with_state(state) -} diff --git a/crates/goose/src/context_mgmt/auto_compact.rs b/crates/goose/src/context_mgmt/auto_compact.rs index 6e31aecf9ac8..ebd8a21c8b71 100644 --- a/crates/goose/src/context_mgmt/auto_compact.rs +++ b/crates/goose/src/context_mgmt/auto_compact.rs @@ -311,7 +311,6 @@ mod tests { working_dir: PathBuf::from(working_dir), description: "Test session".to_string(), schedule_id: Some("test_job".to_string()), - project_id: None, total_tokens: Some(100), input_tokens: Some(50), output_tokens: Some(50), @@ -741,7 +740,6 @@ mod tests { comprehensive_metadata.schedule_id, Some("test_job".to_string()) ); - assert!(comprehensive_metadata.project_id.is_none()); assert_eq!(comprehensive_metadata.total_tokens, Some(100)); assert_eq!(comprehensive_metadata.input_tokens, Some(50)); assert_eq!(comprehensive_metadata.output_tokens, Some(50)); diff --git a/crates/goose/src/lib.rs b/crates/goose/src/lib.rs index 7d774dddeddc..d0046941f57a 100644 --- a/crates/goose/src/lib.rs +++ b/crates/goose/src/lib.rs @@ -5,7 +5,6 @@ pub mod conversation; pub mod model; pub mod oauth; pub mod permission; -pub mod project; pub mod prompt_template; pub mod providers; pub mod recipe; diff --git a/crates/goose/src/project/mod.rs b/crates/goose/src/project/mod.rs deleted file mode 100644 index 601b47df01c3..000000000000 --- a/crates/goose/src/project/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -pub mod storage; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use utoipa::ToSchema; - -/// Main project structure that holds project metadata and associated sessions -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct Project { - /// Unique identifier for the project - pub id: String, - /// Display name of the project - pub name: String, - /// Optional description of the project - pub description: Option, - /// Default working directory for sessions in this project - #[schema(value_type = String, example = "/home/user/projects/my-project")] - pub default_directory: PathBuf, - /// When the project was created - pub created_at: DateTime, - /// When the project was last updated - pub updated_at: DateTime, - /// List of session IDs associated with this project - pub session_ids: Vec, -} - -/// Simplified project metadata for listing -#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] -#[serde(rename_all = "camelCase")] -pub struct ProjectMetadata { - /// Unique identifier for the project - pub id: String, - /// Display name of the project - pub name: String, - /// Optional description of the project - pub description: Option, - /// Default working directory for sessions in this project - #[schema(value_type = String)] - pub default_directory: PathBuf, - /// Number of sessions in this project - pub session_count: usize, - /// When the project was created - pub created_at: DateTime, - /// When the project was last updated - pub updated_at: DateTime, -} - -impl From<&Project> for ProjectMetadata { - fn from(project: &Project) -> Self { - ProjectMetadata { - id: project.id.clone(), - name: project.name.clone(), - description: project.description.clone(), - default_directory: project.default_directory.clone(), - session_count: project.session_ids.len(), - created_at: project.created_at, - updated_at: project.updated_at, - } - } -} - -// Re-export storage functions -pub use storage::{ - add_session_to_project, create_project, delete_project, ensure_project_dir, get_project, - list_projects, remove_session_from_project, update_project, -}; diff --git a/crates/goose/src/project/storage.rs b/crates/goose/src/project/storage.rs deleted file mode 100644 index ef8e70dc2465..000000000000 --- a/crates/goose/src/project/storage.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::project::{Project, ProjectMetadata}; -use anyhow::{anyhow, Context, Result}; -use chrono::Utc; -use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; -use serde_json; -use std::fs::{self, File}; -use std::io::Write; -use std::path::PathBuf; -use tracing::{error, info}; - -const APP_NAME: &str = "goose"; - -/// Ensure the project directory exists and return its path -pub fn ensure_project_dir() -> Result { - let app_strategy = AppStrategyArgs { - top_level_domain: "Block".to_string(), - author: "Block".to_string(), - app_name: APP_NAME.to_string(), - }; - - let data_dir = choose_app_strategy(app_strategy) - .context("goose requires a home dir")? - .data_dir() - .join("projects"); - - if !data_dir.exists() { - fs::create_dir_all(&data_dir)?; - } - - Ok(data_dir) -} - -/// Generate a unique project ID -fn generate_project_id() -> String { - use rand::Rng; - let timestamp = Utc::now().timestamp(); - let random: u32 = rand::thread_rng().gen(); - format!("proj_{}_{}", timestamp, random) -} - -/// Get the path for a specific project file -fn get_project_path(project_id: &str) -> Result { - let project_dir = ensure_project_dir()?; - Ok(project_dir.join(format!("{}.json", project_id))) -} - -/// Create a new project -pub fn create_project( - name: String, - description: Option, - default_directory: PathBuf, -) -> Result { - let project_dir = ensure_project_dir()?; - - // Validate the default directory exists - if !default_directory.exists() { - return Err(anyhow!( - "Default directory does not exist: {:?}", - default_directory - )); - } - - let now = Utc::now(); - let project = Project { - id: generate_project_id(), - name, - description, - default_directory, - created_at: now, - updated_at: now, - session_ids: Vec::new(), - }; - - // Save the project - let project_path = project_dir.join(format!("{}.json", project.id)); - let mut file = File::create(&project_path)?; - let json = serde_json::to_string_pretty(&project)?; - file.write_all(json.as_bytes())?; - - info!("Created project {} at {:?}", project.id, project_path); - Ok(project) -} - -/// Update an existing project -pub fn update_project( - project_id: &str, - name: Option, - description: Option>, - default_directory: Option, -) -> Result { - let project_path = get_project_path(project_id)?; - - if !project_path.exists() { - return Err(anyhow!("Project not found: {}", project_id)); - } - - // Read existing project - let mut project: Project = serde_json::from_reader(File::open(&project_path)?)?; - - // Update fields - if let Some(new_name) = name { - project.name = new_name; - } - - if let Some(new_description) = description { - project.description = new_description; - } - - if let Some(new_directory) = default_directory { - if !new_directory.exists() { - return Err(anyhow!( - "Default directory does not exist: {:?}", - new_directory - )); - } - project.default_directory = new_directory; - } - - project.updated_at = Utc::now(); - - // Save updated project - let mut file = File::create(&project_path)?; - let json = serde_json::to_string_pretty(&project)?; - file.write_all(json.as_bytes())?; - - info!("Updated project {}", project_id); - Ok(project) -} - -/// Delete a project (does not delete associated sessions) -pub fn delete_project(project_id: &str) -> Result<()> { - let project_path = get_project_path(project_id)?; - - if !project_path.exists() { - return Err(anyhow!("Project not found: {}", project_id)); - } - - fs::remove_file(&project_path)?; - info!("Deleted project {}", project_id); - Ok(()) -} - -/// List all projects -pub fn list_projects() -> Result> { - let project_dir = ensure_project_dir()?; - let mut projects = Vec::new(); - - if let Ok(entries) = fs::read_dir(&project_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.extension().and_then(|s| s.to_str()) == Some("json") { - match serde_json::from_reader::<_, Project>(File::open(&path)?) { - Ok(project) => { - projects.push(ProjectMetadata::from(&project)); - } - Err(e) => { - error!("Failed to read project file {:?}: {}", path, e); - } - } - } - } - } - - // Sort by updated_at descending - projects.sort_by(|a, b| b.updated_at.cmp(&a.updated_at)); - - Ok(projects) -} - -/// Get a specific project -pub fn get_project(project_id: &str) -> Result { - let project_path = get_project_path(project_id)?; - - if !project_path.exists() { - return Err(anyhow!("Project not found: {}", project_id)); - } - - let project: Project = serde_json::from_reader(File::open(&project_path)?)?; - Ok(project) -} - -/// Add a session to a project -pub fn add_session_to_project(project_id: &str, session_id: &str) -> Result<()> { - let project_path = get_project_path(project_id)?; - - if !project_path.exists() { - return Err(anyhow!("Project not found: {}", project_id)); - } - - // Read project - let mut project: Project = serde_json::from_reader(File::open(&project_path)?)?; - - // Check if session already exists in project - if project.session_ids.contains(&session_id.to_string()) { - return Ok(()); // Already added - } - - // Add session and update timestamp - project.session_ids.push(session_id.to_string()); - project.updated_at = Utc::now(); - - // Save updated project - let mut file = File::create(&project_path)?; - let json = serde_json::to_string_pretty(&project)?; - file.write_all(json.as_bytes())?; - - info!("Added session {} to project {}", session_id, project_id); - Ok(()) -} - -/// Remove a session from a project -pub fn remove_session_from_project(project_id: &str, session_id: &str) -> Result<()> { - let project_path = get_project_path(project_id)?; - - if !project_path.exists() { - return Err(anyhow!("Project not found: {}", project_id)); - } - - // Read project - let mut project: Project = serde_json::from_reader(File::open(&project_path)?)?; - - // Remove session - let original_len = project.session_ids.len(); - project.session_ids.retain(|id| id != session_id); - - if project.session_ids.len() == original_len { - return Ok(()); // Session wasn't in project - } - - project.updated_at = Utc::now(); - - // Save updated project - let mut file = File::create(&project_path)?; - let json = serde_json::to_string_pretty(&project)?; - file.write_all(json.as_bytes())?; - - info!("Removed session {} from project {}", session_id, project_id); - Ok(()) -} diff --git a/crates/goose/src/scheduler.rs b/crates/goose/src/scheduler.rs index 0a4931751e43..0116e203fb30 100644 --- a/crates/goose/src/scheduler.rs +++ b/crates/goose/src/scheduler.rs @@ -1291,7 +1291,6 @@ async fn run_scheduled_job_internal( working_dir: current_dir.clone(), description: String::new(), schedule_id: Some(job.id.clone()), - project_id: None, message_count: all_session_messages.len(), total_tokens: None, input_tokens: None, diff --git a/crates/goose/src/session/storage.rs b/crates/goose/src/session/storage.rs index 5cc3e3a457da..9fc8bc6aafdb 100644 --- a/crates/goose/src/session/storage.rs +++ b/crates/goose/src/session/storage.rs @@ -49,8 +49,7 @@ pub struct SessionMetadata { pub description: String, /// ID of the schedule that triggered this session, if any pub schedule_id: Option, - /// ID of the project this session belongs to, if any - pub project_id: Option, + /// Number of messages in the session pub message_count: usize, /// The total number of tokens used in the session. Retrieved from the provider's last usage. @@ -78,7 +77,6 @@ impl<'de> Deserialize<'de> for SessionMetadata { description: String, message_count: usize, schedule_id: Option, // For backward compatibility - project_id: Option, // For backward compatibility total_tokens: Option, input_tokens: Option, output_tokens: Option, @@ -100,7 +98,6 @@ impl<'de> Deserialize<'de> for SessionMetadata { description: helper.description, message_count: helper.message_count, schedule_id: helper.schedule_id, - project_id: helper.project_id, total_tokens: helper.total_tokens, input_tokens: helper.input_tokens, output_tokens: helper.output_tokens, @@ -125,7 +122,6 @@ impl SessionMetadata { working_dir, description: String::new(), schedule_id: None, - project_id: None, message_count: 0, total_tokens: None, input_tokens: None, diff --git a/crates/goose/tests/test_support.rs b/crates/goose/tests/test_support.rs index a2a3a2e5f6e8..0b0632721e01 100644 --- a/crates/goose/tests/test_support.rs +++ b/crates/goose/tests/test_support.rs @@ -405,7 +405,6 @@ pub fn create_test_session_metadata(message_count: usize, working_dir: &str) -> working_dir: PathBuf::from(working_dir), description: "Test session".to_string(), schedule_id: Some("test_job".to_string()), - project_id: None, total_tokens: Some(100), input_tokens: Some(50), output_tokens: Some(50), diff --git a/ui/desktop/openapi.json b/ui/desktop/openapi.json index 589b3987dbb2..94012c8c4b06 100644 --- a/ui/desktop/openapi.json +++ b/ui/desktop/openapi.json @@ -2790,11 +2790,6 @@ "description": "The number of output tokens used in the session. Retrieved from the provider's last usage.", "nullable": true }, - "project_id": { - "type": "string", - "description": "ID of the project this session belongs to, if any", - "nullable": true - }, "schedule_id": { "type": "string", "description": "ID of the schedule that triggered this session, if any", diff --git a/ui/desktop/scripts/copy-windows-dlls.js b/ui/desktop/scripts/copy-windows-dlls.js deleted file mode 100644 index d95a6bfd992a..000000000000 --- a/ui/desktop/scripts/copy-windows-dlls.js +++ /dev/null @@ -1,49 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// Paths -const platformWinDir = path.join(__dirname, '..', 'src', 'platform', 'windows', 'bin'); -const outDir = path.join(__dirname, '..', 'out', 'Goose-win32-x64', 'resources', 'bin'); -const srcBinDir = path.join(__dirname, '..', 'src', 'bin'); - -// Helper function to copy files -function copyFiles(sourceDir, targetDir) { - // Ensure target directory exists - if (!fs.existsSync(targetDir)) { - fs.mkdirSync(targetDir, { recursive: true }); - } - - // Copy all files from source to target directory - console.log(`Copying files to ${targetDir}...`); - fs.readdirSync(sourceDir).forEach(file => { - const srcPath = path.join(sourceDir, file); - const destPath = path.join(targetDir, file); - - // Skip .gitignore and README.md - if (file === '.gitignore' || file === 'README.md') { - return; - } - - // Handle directories (like goose-npm) - if (fs.statSync(srcPath).isDirectory()) { - fs.cpSync(srcPath, destPath, { recursive: true, force: true }); - console.log(`Copied directory: ${file}`); - return; - } - - // Copy individual file - fs.copyFileSync(srcPath, destPath); - console.log(`Copied: ${file}`); - }); -} - -// Copy to both directories -console.log('Copying Windows-specific files...'); - -// Copy to src/bin for development -copyFiles(platformWinDir, srcBinDir); - -// Copy to output directory for distribution -copyFiles(platformWinDir, outDir); - -console.log('Windows-specific files copied successfully'); diff --git a/ui/desktop/scripts/generate-update-icon.js b/ui/desktop/scripts/generate-update-icon.js deleted file mode 100644 index edf533ce6d18..000000000000 --- a/ui/desktop/scripts/generate-update-icon.js +++ /dev/null @@ -1,53 +0,0 @@ -const { createCanvas, loadImage } = require('canvas'); -const fs = require('fs'); -const path = require('path'); - -async function generateUpdateIcon() { - // Load the original icon - const iconPath = path.join(__dirname, '../src/images/iconTemplate.png'); - const icon = await loadImage(iconPath); - - // Create canvas - const canvas = createCanvas(22, 22); - const ctx = canvas.getContext('2d'); - - // Draw the original icon - ctx.drawImage(icon, 0, 0); - - // Add red dot in top-right corner - ctx.fillStyle = '#FF0000'; - ctx.beginPath(); - ctx.arc(18, 4, 3, 0, 2 * Math.PI); - ctx.fill(); - - // Save the new icon - const outputPath = path.join(__dirname, '../src/images/iconTemplateUpdate.png'); - const buffer = canvas.toBuffer('image/png'); - fs.writeFileSync(outputPath, buffer); - - console.log('Generated update icon at:', outputPath); - - // Also generate @2x version - const canvas2x = createCanvas(44, 44); - const ctx2x = canvas2x.getContext('2d'); - - // Load and draw @2x version - const icon2xPath = path.join(__dirname, '../src/images/iconTemplate@2x.png'); - const icon2x = await loadImage(icon2xPath); - ctx2x.drawImage(icon2x, 0, 0); - - // Add red dot in top-right corner (scaled) - ctx2x.fillStyle = '#FF0000'; - ctx2x.beginPath(); - ctx2x.arc(36, 8, 6, 0, 2 * Math.PI); - ctx2x.fill(); - - // Save the @2x version - const output2xPath = path.join(__dirname, '../src/images/iconTemplateUpdate@2x.png'); - const buffer2x = canvas2x.toBuffer('image/png'); - fs.writeFileSync(output2xPath, buffer2x); - - console.log('Generated @2x update icon at:', output2xPath); -} - -generateUpdateIcon().catch(console.error); \ No newline at end of file diff --git a/ui/desktop/scripts/prepare-platform.js b/ui/desktop/scripts/prepare-platform.js deleted file mode 100644 index 3a06d21c0691..000000000000 --- a/ui/desktop/scripts/prepare-platform.js +++ /dev/null @@ -1,16 +0,0 @@ -const { execSync } = require('child_process'); -const path = require('path'); - -try { - if (process.platform === 'win32') { - execSync(path.join(__dirname, 'prepare-windows-npm.bat'), { stdio: 'inherit' }); - } else { - execSync(path.join(__dirname, 'prepare-windows-npm.sh'), { - stdio: 'inherit', - shell: '/bin/bash' - }); - } -} catch (error) { - console.error('Error preparing platform:', error); - process.exit(1); -} \ No newline at end of file diff --git a/ui/desktop/src/App.tsx b/ui/desktop/src/App.tsx index 2fc1173d19df..5636f0c7c426 100644 --- a/ui/desktop/src/App.tsx +++ b/ui/desktop/src/App.tsx @@ -637,57 +637,6 @@ const ExtensionsRoute = () => { ); }; -// const ProjectsRoute = () => { -// const navigate = useNavigate(); -// -// const setView = (view: View, viewOptions?: ViewOptions) => { -// // Convert view to route navigation -// switch (view) { -// case 'chat': -// navigate('/'); -// break; -// case 'pair': -// navigate('/pair', { state: viewOptions }); -// break; -// case 'settings': -// navigate('/settings', { state: viewOptions }); -// break; -// case 'sessions': -// navigate('/sessions'); -// break; -// case 'schedules': -// navigate('/schedules'); -// break; -// case 'recipes': -// navigate('/recipes'); -// break; -// case 'permission': -// navigate('/permission', { state: viewOptions }); -// break; -// case 'ConfigureProviders': -// navigate('/configure-providers'); -// break; -// case 'sharedSession': -// navigate('/shared-session', { state: viewOptions }); -// break; -// case 'recipeEditor': -// navigate('/recipe-editor', { state: viewOptions }); -// break; -// case 'welcome': -// navigate('/welcome'); -// break; -// default: -// navigate('/'); -// } -// }; -// -// return ( -// Loading projects...}> -// -// -// ); -// }; - export default function App() { const [fatalError, setFatalError] = useState(null); const [modalVisible, setModalVisible] = useState(false); @@ -1488,16 +1437,6 @@ export default function App() { } /> - {/**/} - {/* */} - {/* */} - {/* */} - {/* */} - {/* }*/} - {/*/>*/} diff --git a/ui/desktop/src/api/types.gen.ts b/ui/desktop/src/api/types.gen.ts index eeb5a691b218..69e194b2f5a8 100644 --- a/ui/desktop/src/api/types.gen.ts +++ b/ui/desktop/src/api/types.gen.ts @@ -649,10 +649,6 @@ export type SessionMetadata = { * The number of output tokens used in the session. Retrieved from the provider's last usage. */ output_tokens?: number | null; - /** - * ID of the project this session belongs to, if any - */ - project_id?: string | null; /** * ID of the schedule that triggered this session, if any */ diff --git a/ui/desktop/src/components/ApiKeyWarning.tsx b/ui/desktop/src/components/ApiKeyWarning.tsx deleted file mode 100644 index 3821ff38c16d..000000000000 --- a/ui/desktop/src/components/ApiKeyWarning.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import React from 'react'; -import { Card } from './ui/card'; -import { Bird } from './ui/icons'; -import { ChevronDown } from './icons'; - -interface ApiKeyWarningProps { - className?: string; -} - -interface CollapsibleProps { - title: string; - children: React.ReactNode; - defaultOpen?: boolean; -} - -function Collapsible({ title, children, defaultOpen = false }: CollapsibleProps) { - const [isOpen, setIsOpen] = React.useState(defaultOpen); - - return ( -
- - {isOpen &&
{children}
} -
- ); -} - -const OPENAI_CONFIG = `export GOOSE_PROVIDER__TYPE=openai -export GOOSE_PROVIDER__HOST=https://api.openai.com -export GOOSE_PROVIDER__MODEL=gpt-4o -export GOOSE_PROVIDER__API_KEY=your_api_key_here`; - -const ANTHROPIC_CONFIG = `export GOOSE_PROVIDER__TYPE=anthropic -export GOOSE_PROVIDER__HOST=https://api.anthropic.com -export GOOSE_PROVIDER__MODEL=claude-3-5-sonnet-latest -export GOOSE_PROVIDER__API_KEY=your_api_key_here`; - -const DATABRICKS_CONFIG = `export GOOSE_PROVIDER__TYPE=databricks -export GOOSE_PROVIDER__HOST=your_databricks_host -export GOOSE_PROVIDER__MODEL=your_databricks_model`; - -const OPENROUTER_CONFIG = `export GOOSE_PROVIDER__TYPE=openrouter -export GOOSE_PROVIDER__HOST=https://openrouter.ai -export GOOSE_PROVIDER__MODEL=anthropic/claude-3.5-sonnet -export GOOSE_PROVIDER__API_KEY=your_api_key_here`; - -export function ApiKeyWarning({ className }: ApiKeyWarningProps) { - return ( - -
- -
-
-

Credentials Required

-

- To use Goose, you need to set environment variables for one of the following providers: -

- -
- -
{OPENAI_CONFIG}
-
- - -
{ANTHROPIC_CONFIG}
-
- - -
{DATABRICKS_CONFIG}
-
- - -
{OPENROUTER_CONFIG}
-
-
-

- After setting these variables, restart Goose for the changes to take effect. -

-
-
- ); -} diff --git a/ui/desktop/src/components/LoadingPlaceholder.tsx b/ui/desktop/src/components/LoadingPlaceholder.tsx deleted file mode 100644 index 248688cdba84..000000000000 --- a/ui/desktop/src/components/LoadingPlaceholder.tsx +++ /dev/null @@ -1,16 +0,0 @@ -export function LoadingPlaceholder() { - return ( -
- {[...Array(2)].map((_, index) => ( -
- ))} -
- ); -} diff --git a/ui/desktop/src/components/Modal.tsx b/ui/desktop/src/components/Modal.tsx deleted file mode 100644 index b4d88383bd3f..000000000000 --- a/ui/desktop/src/components/Modal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { Card } from './ui/card'; - -interface ModalProps { - children: React.ReactNode; - footer?: React.ReactNode; // Optional footer - onClose: () => void; // Function to call when modal should close - preventBackdropClose?: boolean; // Optional prop to prevent closing on backdrop click -} - -/** - * A reusable modal component that renders content with a semi-transparent backdrop and blur effect. - * Closes when clicking outside the modal or pressing Esc key. - */ -export default function Modal({ - children, - footer, - onClose, - preventBackdropClose = false, -}: ModalProps) { - const modalRef = useRef(null); - - // Handle click outside the modal content - const handleBackdropClick = (e: React.MouseEvent) => { - if (preventBackdropClose) return; - // Check if the click was on the backdrop and not on the modal content - // Also check if the click target is not part of a Select menu - if ( - modalRef.current && - !modalRef.current.contains(e.target as Node) && - !(e.target as HTMLElement).closest('.select__menu') && - window.getSelection()?.toString().length === 0 // Ensure no text is selected - ) { - onClose(); - } - }; - - // Handle Esc key press - useEffect(() => { - const handleEscKey = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - // Don't close if a select menu is open - const selectMenu = document.querySelector('.select__menu'); - if (!selectMenu) { - onClose(); - } - } - }; - - // Add event listener for Escape key - document.addEventListener('keydown', handleEscKey); - - // Add overflow-hidden to body to prevent scrolling background - document.body.style.overflow = 'hidden'; - - // Clean up - return () => { - document.removeEventListener('keydown', handleEscKey); - // Restore body scrolling when modal closes - document.body.style.overflow = ''; - }; - }, [onClose]); - - return ( -
- -
{children}
- {footer && ( -
- {footer} -
- )} -
-
- ); -} diff --git a/ui/desktop/src/components/SplashPills.tsx b/ui/desktop/src/components/SplashPills.tsx deleted file mode 100644 index 18506b757658..000000000000 --- a/ui/desktop/src/components/SplashPills.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import MarkdownContent from './MarkdownContent'; - -function truncateText(text: string, maxLength: number = 100): string { - if (text.length <= maxLength) return text; - return text.slice(0, maxLength) + '...'; -} - -interface SplashPillProps { - content: string; - append: (text: string) => void; - className?: string; -} - -function SplashPill({ content, append, className = '' }: SplashPillProps) { - const displayText = truncateText(content); - - return ( -
{ - // Always use the full text (longForm or original content) when clicked - await append(content); - }} - title={content.length > 100 ? content : undefined} // Show full text on hover if truncated - > -
{displayText}
-
- ); -} - -interface ContextBlockProps { - content: string; -} - -function ContextBlock({ content }: ContextBlockProps) { - // Remove the "message:" prefix and trim whitespace - const displayText = content.replace(/^message:/i, '').trim(); - - return ( -
- -
- ); -} - -interface SplashPillsProps { - append: (text: string) => void; - activities: string[] | null; -} - -export default function SplashPills({ append, activities = null }: SplashPillsProps) { - // If custom activities are provided, use those instead of the default ones - const defaultPills = [ - 'What can you do?', - 'Demo writing and reading files', - 'Make a snake game in a new folder', - 'List files in my current directory', - 'Take a screenshot and summarize', - ]; - - const pills = activities || defaultPills; - - // Find any pill that starts with "message:" - const messagePillIndex = pills.findIndex((pill) => pill.toLowerCase().startsWith('message:')); - - // Extract the message pill and the remaining pills - const messagePill = messagePillIndex >= 0 ? pills[messagePillIndex] : null; - const remainingPills = - messagePillIndex >= 0 - ? [...pills.slice(0, messagePillIndex), ...pills.slice(messagePillIndex + 1)] - : pills; - - return ( -
- {messagePill && } - -
- {remainingPills.map((content, index) => ( - - ))} -
-
- ); -} diff --git a/ui/desktop/src/components/cli/CLIChatView.tsx b/ui/desktop/src/components/cli/CLIChatView.tsx deleted file mode 100644 index 0dc500b92ea0..000000000000 --- a/ui/desktop/src/components/cli/CLIChatView.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; - -interface CLIChatViewProps { - sessionId: string; - // onSessionExit: () => void; -} - -export const CLIChatView: React.FC = ({ sessionId }) => { - return ( -
-
-

CLI Chat View for session: {sessionId}

-

This component is under development.

-
-
- ); -}; diff --git a/ui/desktop/src/components/cli/CLIHub.tsx b/ui/desktop/src/components/cli/CLIHub.tsx deleted file mode 100644 index fb8ab46c0b94..000000000000 --- a/ui/desktop/src/components/cli/CLIHub.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { CLIChatView } from './CLIChatView'; -import { useNavigate } from 'react-router-dom'; -import { Button } from '../ui/button'; -import { Badge } from '../ui/badge'; -import { Terminal, Settings, History, MessageSquare, ArrowLeft, RefreshCw } from 'lucide-react'; -import { generateSessionId } from '../../sessions'; -import { type View, ViewOptions } from '../../App'; -import { MainPanelLayout } from '../Layout/MainPanelLayout'; - -interface ChatMessage { - role: string; - content: string; - id?: string; - timestamp?: number; - [key: string]: unknown; -} - -interface ChatState { - id: string; - title: string; - messageHistoryIndex: number; - messages: ChatMessage[]; -} - -interface CLIHubProps { - chat: ChatState; - setChat: (chat: ChatState) => void; - setView: (view: View, viewOptions?: ViewOptions) => void; -} - -export const CLIHub: React.FC = ({ chat, setChat, setView }) => { - const navigate = useNavigate(); - const [sessionId, setSessionId] = useState(chat.id || generateSessionId()); - - // Update chat when session changes - useEffect(() => { - setChat({ - ...chat, - id: sessionId, - title: `CLI Session - ${sessionId}`, - messageHistoryIndex: 0, - messages: [], - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sessionId, setChat]); - - const handleNewSession = () => { - const newSessionId = generateSessionId(); - setSessionId(newSessionId); - }; - - const handleRestartSession = () => { - // Force restart by changing session ID - const newSessionId = generateSessionId(); - setSessionId(newSessionId); - }; - - return ( - - {/* Header */} -
-
- - - -
-

Goose CLI Experience

-

- Exact CLI behavior with GUI enhancements -

-
-
- -
- - CLI Mode - - - - - - - -
-
- - {/* CLI Chat View */} -
- -
- - {/* Footer */} -
-
-
- Session: {sessionId} - - CLI Mode Active -
- -
- - - -
-
-
-
- ); -}; diff --git a/ui/desktop/src/components/common/ActivityHeatmap.tsx b/ui/desktop/src/components/common/ActivityHeatmap.tsx deleted file mode 100644 index e0b116ff406b..000000000000 --- a/ui/desktop/src/components/common/ActivityHeatmap.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { useEffect, useState } from 'react'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip'; -import { getApiUrl } from '../../config'; - -interface ActivityHeatmapCell { - week: number; - day: number; - count: number; - date?: string; // Add date for better display in tooltips -} - -// Days of the week for labeling -const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; -// Number of weeks in a year -const WEEKS_IN_YEAR = 52; - -export function ActivityHeatmap() { - const [heatmapData, setHeatmapData] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - const [currentYear] = useState(new Date().getFullYear()); - - // Calculate the intensity for coloring cells - const getColorIntensity = (count: number, maxCount: number) => { - if (count === 0) return 'bg-background-muted/30'; - - const normalizedCount = count / maxCount; - - if (normalizedCount < 0.25) return 'bg-background-accent/20'; - if (normalizedCount < 0.5) return 'bg-background-accent/40'; - if (normalizedCount < 0.75) return 'bg-background-accent/60'; - return 'bg-background-accent/80'; - }; - - useEffect(() => { - const fetchHeatmapData = async () => { - try { - setLoading(true); - const response = await fetch(getApiUrl('/sessions/activity-heatmap'), { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - 'X-Secret-Key': await window.electron.getSecretKey(), - }, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch heatmap data: ${response.status}`); - } - - const data = await response.json(); - setHeatmapData(data); - setError(null); - } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to load heatmap data'); - } finally { - setLoading(false); - } - }; - - fetchHeatmapData(); - }, []); - - // Find the maximum count for scaling - const maxCount = Math.max( - 1, // Avoid division by zero - ...heatmapData.map((cell) => cell.count) - ); - - // Create a calendar grid from Jan 1st of current year to today - const prepareGridData = () => { - // Get current date - const now = new Date(); - const startOfYear = new Date(currentYear, 0, 1); // Jan 1st of current year - - // Calculate weeks to display - now showing full year (52 weeks) - // const weeksToDisplay = Math.ceil((daysSinceStartOfYear + getStartDayOfYear()) / 7); - const weeksToDisplay = WEEKS_IN_YEAR; - - // Create a map to lookup counts easily - const dataMap = new Map(); - heatmapData.forEach((cell) => { - dataMap.set(`${cell.week}-${cell.day}`, cell.count); - }); - - // Build the grid - const grid = []; - - // Fill grid with dates and activity data - for (let week = 0; week < weeksToDisplay; week++) { - const weekCells = []; - - for (let day = 0; day < 7; day++) { - // Convert week and day to a real date - const cellDate = new Date(startOfYear); - cellDate.setDate(cellDate.getDate() + week * 7 + day - getStartDayOfYear()); - - // Only include dates up to today for real data - const isFuture = cellDate > now; - - // Format the date string - const dateStr = cellDate.toLocaleDateString(undefined, { - month: 'short', - day: 'numeric', - }); - - // Get count from data if available - let count = 0; - - // Try to find a matching date in our data - // This requires matching the specific week number (from ISO week) and day - if (!isFuture) { - for (const cell of heatmapData) { - if (cell.week === getWeekNumber(cellDate) && cell.day === day) { - count = cell.count; - break; - } - } - } - - weekCells.push({ - week, - day, - count, - date: dateStr, - }); - } - - grid.push(weekCells); - } - - return grid; - }; - - // Helper to get day of week (0-6) of Jan 1st for current year - const getStartDayOfYear = () => { - return new Date(currentYear, 0, 1).getDay(); - }; - - // Get ISO week number for a date - const getWeekNumber = (date: Date) => { - const d = new Date(date); - d.setHours(0, 0, 0, 0); - d.setDate(d.getDate() + 3 - ((d.getDay() + 6) % 7)); - const week1 = new Date(d.getFullYear(), 0, 4); - return ( - 1 + - Math.round(((d.getTime() - week1.getTime()) / 86400000 - 3 + ((week1.getDay() + 6) % 7)) / 7) - ); - }; - - const grid = prepareGridData(); - - if (loading) { - return ( -
-
Loading activity data...
-
- ); - } - - if (error) { - return ( -
-
Error loading activity data
-
- ); - } - - // Get month labels - now showing all months - const getMonthLabels = () => { - const allMonths = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - - return allMonths.map((month, i) => { - // Calculate position based on days in month and start day of year - const monthIndex = i; - const daysBeforeMonth = getDaysBeforeMonth(monthIndex); - const position = (daysBeforeMonth - getStartDayOfYear()) / 7; - - return ( -
- {month} -
- ); - }); - }; - - // Helper to calculate days before a month in current year - const getDaysBeforeMonth = (monthIndex: number) => { - const days = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334]; - // Adjust for leap year - if (monthIndex > 1 && isLeapYear(currentYear)) { - return days[monthIndex] + 1; - } - return days[monthIndex]; - }; - - // Helper to check if year is a leap year - const isLeapYear = (year: number) => { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - }; - - return ( -
- {/* Month labels */} -
{getMonthLabels()}
- -
- {/* Day labels - now right-aligned */} -
- {DAYS.map((day, index) => ( -
- {index % 2 === 0 ? day : ''} -
- ))} -
- - {/* Grid - with smaller squares */} -
- {grid.map((week, weekIndex) => ( -
- {week.map((cell) => ( - - - -
- - - {cell.date ? ( -

- {cell.count} sessions on {cell.date} -

- ) : ( -

No data

- )} -
- - - ))} -
- ))} -
-
-
- ); -} diff --git a/ui/desktop/src/components/icons/Bars.tsx b/ui/desktop/src/components/icons/Bars.tsx deleted file mode 100644 index f878c3af4262..000000000000 --- a/ui/desktop/src/components/icons/Bars.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export function Bars() { - return ( - - - - - - ); -} diff --git a/ui/desktop/src/components/projects/AddSessionToProjectModal.tsx b/ui/desktop/src/components/projects/AddSessionToProjectModal.tsx deleted file mode 100644 index 0dd2a2702b70..000000000000 --- a/ui/desktop/src/components/projects/AddSessionToProjectModal.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React, { useState } from 'react'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '../ui/dialog'; -import { Button } from '../ui/button'; -import { Session } from '../../sessions'; -import { Project } from '../../projects'; -import { addSessionToProject } from '../../projects'; -import { toastError, toastSuccess } from '../../toasts'; -import { ScrollArea } from '../ui/scroll-area'; -import { Checkbox } from '../ui/checkbox'; -import { formatDistanceToNow } from 'date-fns'; - -interface AddSessionToProjectModalProps { - isOpen: boolean; - onClose: () => void; - project: Project; - availableSessions: Session[]; - onSessionsAdded: () => void; -} - -const AddSessionToProjectModal: React.FC = ({ - isOpen, - onClose, - project, - availableSessions, - onSessionsAdded, -}) => { - const [selectedSessions, setSelectedSessions] = useState([]); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleToggleSession = (sessionId: string) => { - setSelectedSessions((prev) => { - if (prev.includes(sessionId)) { - return prev.filter((id) => id !== sessionId); - } else { - return [...prev, sessionId]; - } - }); - }; - - const handleClose = () => { - setSelectedSessions([]); - onClose(); - }; - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (selectedSessions.length === 0) { - toastError({ title: 'Error', msg: 'Please select at least one session' }); - return; - } - - setIsSubmitting(true); - - try { - // Add each selected session to the project - const promises = selectedSessions.map((sessionId) => - addSessionToProject(project.id, sessionId) - ); - - await Promise.all(promises); - - toastSuccess({ - title: 'Success', - msg: `Added ${selectedSessions.length} ${selectedSessions.length === 1 ? 'session' : 'sessions'} to project`, - }); - onSessionsAdded(); - handleClose(); - } catch (err) { - console.error('Failed to add sessions to project:', err); - toastError({ title: 'Error', msg: 'Failed to add sessions to project' }); - } finally { - setIsSubmitting(false); - } - }; - - return ( - - -
- - Add Sessions to Project - Select sessions to add to "{project.name}" - - - {availableSessions.length === 0 ? ( -
-

- No available sessions to add. All sessions are already part of this project. -

-
- ) : ( - -
- {availableSessions.map((session) => ( -
- handleToggleSession(session.id)} - /> -
- -

- {session.metadata.working_dir} -

-
-
- ))} -
-
- )} - - - - - -
-
-
- ); -}; - -export default AddSessionToProjectModal; diff --git a/ui/desktop/src/components/projects/CreateProjectModal.tsx b/ui/desktop/src/components/projects/CreateProjectModal.tsx deleted file mode 100644 index c11ac5dad22d..000000000000 --- a/ui/desktop/src/components/projects/CreateProjectModal.tsx +++ /dev/null @@ -1,151 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '../ui/dialog'; -import { Button } from '../ui/button'; -import { Input } from '../ui/input'; -import { Label } from '../ui/label'; -import { Textarea } from '../ui/textarea'; -import { FolderSearch } from 'lucide-react'; -import { toastError } from '../../toasts'; - -interface CreateProjectModalProps { - isOpen: boolean; - onClose: () => void; - onCreate: (name: string, description: string, defaultDirectory: string) => void; - defaultDirectory?: string; -} - -const CreateProjectModal: React.FC = ({ - isOpen, - onClose, - onCreate, - defaultDirectory: defaultDirectoryProp, -}) => { - const [name, setName] = useState(''); - const [description, setDescription] = useState(''); - const [defaultDirectory, setDefaultDirectory] = useState(defaultDirectoryProp || ''); - const [isSubmitting, setIsSubmitting] = useState(false); - - useEffect(() => { - if (isOpen) { - setDefaultDirectory(defaultDirectoryProp || ''); - } - }, [defaultDirectoryProp, isOpen]); - - const resetForm = () => { - setName(''); - setDescription(''); - setDefaultDirectory(defaultDirectoryProp || ''); - setIsSubmitting(false); - }; - - const handleClose = () => { - resetForm(); - onClose(); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!name.trim()) { - toastError({ title: 'Error', msg: 'Project name is required' }); - return; - } - - setIsSubmitting(true); - - // Pass data to parent component - onCreate(name, description, defaultDirectory); - - // Form will be reset when the modal is closed by the parent - // after successful creation - }; - - const handlePickDirectory = async () => { - try { - // Use Electron's dialog to pick a directory - const directory = await window.electron.directoryChooser(); - - if (!directory.canceled && directory.filePaths.length > 0) { - setDefaultDirectory(directory.filePaths[0]); - } - } catch (err) { - console.error('Failed to pick directory:', err); - toastError({ title: 'Error', msg: 'Failed to pick directory' }); - } - }; - - return ( - - -
- - Create new project - - Create a project to group related sessions together - - - -
-
- - setName(e.target.value)} - placeholder="My Project" - autoFocus - required - /> -
- -
- -