diff --git a/crates/server/src/bin/generate_types.rs b/crates/server/src/bin/generate_types.rs index 11bdb1af..88b572d7 100644 --- a/crates/server/src/bin/generate_types.rs +++ b/crates/server/src/bin/generate_types.rs @@ -46,6 +46,7 @@ fn generate_types_content() -> String { server::routes::task_attempts::CreateFollowUpAttempt::decl(), server::routes::task_attempts::FollowUpDraftResponse::decl(), server::routes::task_attempts::UpdateFollowUpDraftRequest::decl(), + server::routes::tasks::CreateAndStartTaskRequest::decl(), server::routes::task_attempts::CreateGitHubPrRequest::decl(), server::routes::images::ImageResponse::decl(), services::services::github_service::GitHubServiceError::decl(), diff --git a/crates/server/src/routes/tasks.rs b/crates/server/src/routes/tasks.rs index 2ab6dc21..6c1b5f21 100644 --- a/crates/server/src/routes/tasks.rs +++ b/crates/server/src/routes/tasks.rs @@ -10,17 +10,18 @@ use axum::{ }; use db::models::{ image::TaskImage, - project::Project, task::{CreateTask, Task, TaskWithAttemptStatus, UpdateTask}, task_attempt::{CreateTaskAttempt, TaskAttempt}, }; use deployment::Deployment; +use executors::profile::ExecutorProfileId; use futures_util::TryStreamExt; use serde::Deserialize; use services::services::container::{ ContainerService, WorktreeCleanupData, cleanup_worktrees_direct, }; use sqlx::Error as SqlxError; +use ts_rs::TS; use utils::response::ApiResponse; use uuid::Uuid; @@ -98,15 +99,22 @@ pub async fn create_task( Ok(ResponseJson(ApiResponse::success(task))) } +#[derive(Debug, Deserialize, TS)] +pub struct CreateAndStartTaskRequest { + pub task: CreateTask, + pub executor_profile_id: ExecutorProfileId, + pub base_branch: String, +} + pub async fn create_task_and_start( State(deployment): State, - Json(payload): Json, + Json(payload): Json, ) -> Result>, ApiError> { let task_id = Uuid::new_v4(); - let task = Task::create(&deployment.db().pool, &payload, task_id).await?; + let task = Task::create(&deployment.db().pool, &payload.task, task_id).await?; - if let Some(image_ids) = &payload.image_ids { - TaskImage::associate_many_dedup(&deployment.db().pool, task.id, image_ids).await?; + if let Some(image_ids) = &payload.task.image_ids { + TaskImage::associate_many(&deployment.db().pool, task.id, image_ids).await?; } deployment @@ -116,40 +124,31 @@ pub async fn create_task_and_start( "task_id": task.id.to_string(), "project_id": task.project_id, "has_description": task.description.is_some(), - "has_images": payload.image_ids.is_some(), + "has_images": payload.task.image_ids.is_some(), }), ) .await; - // use the default executor profile and the current branch for the task attempt - let executor_profile_id = deployment.config().read().await.executor_profile.clone(); - let project = Project::find_by_id(&deployment.db().pool, payload.project_id) - .await? - .ok_or(ApiError::Database(SqlxError::RowNotFound))?; - let branch = deployment - .git() - .get_current_branch(&project.git_repo_path)?; - let task_attempt = TaskAttempt::create( &deployment.db().pool, &CreateTaskAttempt { - executor: executor_profile_id.executor, - base_branch: branch, + executor: payload.executor_profile_id.executor, + base_branch: payload.base_branch, }, task.id, ) .await?; let execution_process = deployment .container() - .start_attempt(&task_attempt, executor_profile_id.clone()) + .start_attempt(&task_attempt, payload.executor_profile_id.clone()) .await?; deployment .track_if_analytics_allowed( "task_attempt_started", serde_json::json!({ "task_id": task.id.to_string(), - "executor": &executor_profile_id.executor, - "variant": &executor_profile_id.variant, + "executor": &payload.executor_profile_id.executor, + "variant": &payload.executor_profile_id.variant, "attempt_id": task_attempt.id.to_string(), }), ) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa251c77..745bd235 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,9 +25,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.7", - "@rjsf/core": "^5.24.13", - "@rjsf/utils": "^5.24.13", - "@rjsf/validator-ajv8": "^5.24.13", + "@rjsf/shadcn": "6.0.0-beta.10", "@sentry/react": "^9.34.0", "@sentry/vite-plugin": "^3.5.0", "@tailwindcss/typography": "^0.5.16", @@ -55,6 +53,9 @@ "zustand": "^4.5.4" }, "devDependencies": { + "@rjsf/core": "6.0.0-beta.11", + "@rjsf/utils": "6.0.0-beta.11", + "@rjsf/validator-ajv8": "6.0.0-beta.11", "@tailwindcss/container-queries": "^0.1.1", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", @@ -1739,6 +1740,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -2267,6 +2277,15 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-icons/all-files": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@react-icons/all-files/-/all-files-4.1.0.tgz", + "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -2277,41 +2296,553 @@ } }, "node_modules/@rjsf/core": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz", - "integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.0.0-beta.11.tgz", + "integrity": "sha512-tadVN0B7CjAzl2p3HDsZKR3V8iCIiQMncblfCYSK0PAh9RqIjigFp8ovrZ3UqkVIRRcD+tQ6Y+psY5yEB4GWqA==", "license": "Apache-2.0", "dependencies": { "lodash": "^4.17.21", "lodash-es": "^4.17.21", - "markdown-to-jsx": "^7.4.1", + "markdown-to-jsx": "^7.7.6", + "nanoid": "^5.1.5", "prop-types": "^15.8.1" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^5.24.x", - "react": "^16.14.0 || >=17" + "@rjsf/utils": "^6.0.0-beta", + "react": ">=18" + } + }, + "node_modules/@rjsf/core/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@rjsf/shadcn": { + "version": "6.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@rjsf/shadcn/-/shadcn-6.0.0-beta.10.tgz", + "integrity": "sha512-BXgbCZF+Xb7lqLYBWYRcq4xamB2I41yOx0fT7dNs09CbnyWge4yziE6Ebm7R4N87qf2M320PLdJymcpUe5WtLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-radio-group": "^1.3.4", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-slider": "^1.3.2", + "@radix-ui/react-slot": "^1.2.0", + "@react-icons/all-files": "^4.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lucide-react": "^0.503.0", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@rjsf/core": "^6.0.0-beta", + "@rjsf/utils": "^6.0.0-beta", + "react": ">=18" + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@rjsf/shadcn/node_modules/lucide-react": { + "version": "0.503.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.503.0.tgz", + "integrity": "sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@rjsf/shadcn/node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" } }, "node_modules/@rjsf/utils": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz", - "integrity": "sha512-rNF8tDxIwTtXzz5O/U23QU73nlhgQNYJ+Sv5BAwQOIyhIE2Z3S5tUiSVMwZHt0julkv/Ryfwi+qsD4FiE5rOuw==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.0.0-beta.11.tgz", + "integrity": "sha512-tQeyacweXmquRjPcXCVS1pMFfhamFSoqpczNi0zEvMbx+mfu2b6CgoRKi2Hm8KgFEafdVBF+6HLqs+0FnYhRlQ==", "license": "Apache-2.0", "dependencies": { + "fast-uri": "^3.0.6", "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", + "nanoid": "^5.1.5", "react-is": "^18.2.0" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "react": "^16.14.0 || >=17" + "react": ">=18" + } + }, + "node_modules/@rjsf/utils/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" } }, "node_modules/@rjsf/utils/node_modules/react-is": { @@ -2321,9 +2852,10 @@ "license": "MIT" }, "node_modules/@rjsf/validator-ajv8": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz", - "integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.0.0-beta.11.tgz", + "integrity": "sha512-gR8pbCsR91LiayDKdIz7d7m7A2ozUv9J+7Tpl5/qUQ69Q/NmnHo+cAiVW04/yvqzf1cbRwMgkc5pZyu01Uu1/w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "ajv": "^8.12.0", @@ -2332,16 +2864,17 @@ "lodash-es": "^4.17.21" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^5.24.x" + "@rjsf/utils": "^6.0.0-beta" } }, "node_modules/@rjsf/validator-ajv8/node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2358,6 +2891,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -3604,6 +4138,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -3621,6 +4156,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3637,6 +4173,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { @@ -4705,6 +5242,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -7182,6 +7720,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8048,6 +8587,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/validate.io-array": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", diff --git a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx index bee8dd97..df227f50 100644 --- a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx +++ b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { Globe2 } from 'lucide-react'; +import { Globe2, Settings2, ChevronRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { ImageUploadSection } from '@/components/ui/ImageUploadSection'; import { @@ -18,9 +18,18 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { templatesApi, imagesApi } from '@/lib/api'; +import { templatesApi, imagesApi, projectsApi } from '@/lib/api'; import { useTaskMutations } from '@/hooks/useTaskMutations'; -import type { TaskStatus, TaskTemplate, ImageResponse } from 'shared/types'; +import { useUserSystem } from '@/components/config-provider'; +import { ExecutorProfileSelector } from '@/components/settings'; +import BranchSelector from '@/components/tasks/BranchSelector'; +import type { + TaskStatus, + TaskTemplate, + ImageResponse, + GitBranch, + ExecutorProfileId, +} from 'shared/types'; import NiceModal, { useModal } from '@ebay/nice-modal-react'; interface Task { @@ -45,6 +54,7 @@ export const TaskFormDialog = NiceModal.create( const modal = useModal(); const { createTask, createAndStart, updateTask } = useTaskMutations(projectId); + const { system, profiles } = useUserSystem(); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [status, setStatus] = useState('todo'); @@ -57,6 +67,12 @@ export const TaskFormDialog = NiceModal.create( const [newlyUploadedImageIds, setNewlyUploadedImageIds] = useState< string[] >([]); + const [branches, setBranches] = useState([]); + const [selectedBranch, setSelectedBranch] = useState(''); + const [selectedExecutorProfile, setSelectedExecutorProfile] = + useState(null); + const [quickstartExpanded, setQuickstartExpanded] = + useState(false); const isEditMode = Boolean(task); @@ -135,25 +151,50 @@ export const TaskFormDialog = NiceModal.create( setSelectedTemplate(''); setImages([]); setNewlyUploadedImageIds([]); + setSelectedBranch(''); + setSelectedExecutorProfile(system.config?.executor_profile || null); + setQuickstartExpanded(false); } - }, [task, initialTask, initialTemplate, modal.visible]); + }, [ + task, + initialTask, + initialTemplate, + modal.visible, + system.config?.executor_profile, + ]); - // Fetch templates when dialog opens in create mode + // Fetch templates and branches when dialog opens in create mode useEffect(() => { if (modal.visible && !isEditMode && projectId) { - // Fetch both project and global templates + // Fetch templates and branches Promise.all([ templatesApi.listByProject(projectId), templatesApi.listGlobal(), + projectsApi.getBranches(projectId), ]) - .then(([projectTemplates, globalTemplates]) => { + .then(([projectTemplates, globalTemplates, projectBranches]) => { // Combine templates with project templates first setTemplates([...projectTemplates, ...globalTemplates]); + + // Set branches and default to current branch + setBranches(projectBranches); + const currentBranch = projectBranches.find((b) => b.is_current); + const defaultBranch = currentBranch || projectBranches[0]; + if (defaultBranch) { + setSelectedBranch(defaultBranch.name); + } }) .catch(console.error); } }, [modal.visible, isEditMode, projectId]); + // Set default executor from config (following TaskDetailsToolbar pattern) + useEffect(() => { + if (system.config?.executor_profile) { + setSelectedExecutorProfile(system.config.executor_profile); + } + }, [system.config?.executor_profile]); + // Handle template selection const handleTemplateChange = (templateId: string) => { setSelectedTemplate(templateId); @@ -275,13 +316,27 @@ export const TaskFormDialog = NiceModal.create( ? newlyUploadedImageIds : undefined; + // Use selected executor profile or fallback to config default + const finalExecutorProfile = + selectedExecutorProfile || system.config?.executor_profile; + if (!finalExecutorProfile || !selectedBranch) { + console.warn( + `Missing ${!finalExecutorProfile ? 'executor profile' : 'branch'} for Create & Start` + ); + return; + } + createAndStart.mutate( { - project_id: projectId, - title, - description: description || null, - parent_task_attempt: null, - image_ids: imageIds || null, + task: { + project_id: projectId, + title, + description: description || null, + parent_task_attempt: null, + image_ids: imageIds || null, + }, + executor_profile_id: finalExecutorProfile, + base_branch: selectedBranch, }, { onSuccess: () => { @@ -301,6 +356,9 @@ export const TaskFormDialog = NiceModal.create( modal, newlyUploadedImageIds, createAndStart, + selectedExecutorProfile, + selectedBranch, + system.config?.executor_profile, ]); const handleCancel = useCallback(() => { @@ -505,6 +563,70 @@ export const TaskFormDialog = NiceModal.create( )} + {!isEditMode && + (() => { + const quickstartSection = ( +
+
+ setQuickstartExpanded( + (e.target as HTMLDetailsElement).open + ) + } + > + + + + Quickstart + +
+

+ Configuration for "Create & Start" workflow +

+ + {/* Executor Profile Selector */} + {profiles && selectedExecutorProfile && ( + + )} + + {/* Branch Selector */} + {branches.length > 0 && ( +
+ +
+ +
+
+ )} +
+
+
+ ); + return quickstartSection; + })()} +
+ + + {Object.keys(profiles) + .sort((a, b) => a.localeCompare(b)) + .map((executorKey) => ( + handleExecutorChange(executorKey)} + className={ + selectedProfile?.executor === executorKey ? 'bg-accent' : '' + } + > + {executorKey} + + ))} + + +
+ + {/* Variant Selector (conditional) */} + {showVariantSelector && + selectedProfile && + hasVariants && + currentProfile && ( +
+ + + + + + + {Object.keys(currentProfile).map((variantKey) => ( + handleVariantChange(variantKey)} + className={ + selectedProfile.variant === variantKey ? 'bg-accent' : '' + } + > + {variantKey} + + ))} + + +
+ )} + + {/* Show disabled variant selector for profiles without variants */} + {showVariantSelector && + selectedProfile && + !hasVariants && + currentProfile && ( +
+ + +
+ )} + + {/* Show placeholder for variant when no profile selected */} + {showVariantSelector && !selectedProfile && ( +
+ + +
+ )} + + ); +} + +export default ExecutorProfileSelector; diff --git a/frontend/src/components/settings/TaskSettings.tsx b/frontend/src/components/settings/TaskSettings.tsx new file mode 100644 index 00000000..99f7b7b7 --- /dev/null +++ b/frontend/src/components/settings/TaskSettings.tsx @@ -0,0 +1,96 @@ +import { Label } from '@/components/ui/label'; +import BranchSelector from '@/components/tasks/BranchSelector'; +import ExecutorProfileSelector from './ExecutorProfileSelector'; +import type { + GitBranch, + ExecutorConfig, + ExecutorProfileId, +} from 'shared/types'; + +type Props = { + // Branch selector props + branches?: GitBranch[]; + selectedBranch?: string | null; + onBranchSelect?: (branch: string) => void; + showBranchSelector?: boolean; + branchSelectorProps?: { + placeholder?: string; + className?: string; + excludeCurrentBranch?: boolean; + }; + + // Executor profile selector props + profiles?: Record | null; + selectedProfile?: ExecutorProfileId | null; + onProfileSelect?: (profile: ExecutorProfileId) => void; + showExecutorSelector?: boolean; + executorSelectorProps?: { + showLabel?: boolean; + showVariantSelector?: boolean; + className?: string; + }; + + // Common props + disabled?: boolean; + className?: string; +}; + +function TaskSettings({ + // Branch selector props + branches = [], + selectedBranch, + onBranchSelect, + showBranchSelector = true, + branchSelectorProps = {}, + + // Executor profile selector props + profiles, + selectedProfile, + onProfileSelect, + showExecutorSelector = true, + executorSelectorProps = {}, + + // Common props + disabled = false, + className = '', +}: Props) { + return ( +
+ {/* Executor Profile Selector */} + {showExecutorSelector && + profiles && + selectedProfile && + onProfileSelect && ( + + )} + + {/* Branch Selector */} + {showBranchSelector && + branches.length > 0 && + selectedBranch !== undefined && + onBranchSelect && ( +
+ + +
+ )} +
+ ); +} + +export default TaskSettings; diff --git a/frontend/src/components/settings/index.ts b/frontend/src/components/settings/index.ts new file mode 100644 index 00000000..4db06bdf --- /dev/null +++ b/frontend/src/components/settings/index.ts @@ -0,0 +1,2 @@ +export { default as ExecutorProfileSelector } from './ExecutorProfileSelector'; +export { default as TaskSettings } from './TaskSettings'; diff --git a/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx b/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx index c76f9eed..099c1586 100644 --- a/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +++ b/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx @@ -1,19 +1,14 @@ import { Dispatch, SetStateAction, useCallback } from 'react'; import { Button } from '@/components/ui/button.tsx'; -import { ArrowDown, Settings2, X } from 'lucide-react'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu.tsx'; -import type { BaseCodingAgent, GitBranch, Task } from 'shared/types'; +import { X } from 'lucide-react'; +import type { GitBranch, Task } from 'shared/types'; import type { ExecutorConfig } from 'shared/types'; import type { ExecutorProfileId } from 'shared/types'; import type { TaskAttempt } from 'shared/types'; import { useAttemptCreation } from '@/hooks/useAttemptCreation'; import { useAttemptExecution } from '@/hooks/useAttemptExecution'; import BranchSelector from '@/components/tasks/BranchSelector.tsx'; +import { ExecutorProfileSelector } from '@/components/settings'; import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts.ts'; import { showModal } from '@/lib/modals'; import { Card } from '@/components/ui/card'; @@ -166,136 +161,17 @@ function CreateAttempt({ /> - {/* Step 2: Choose Profile */} -
-
- -
- {availableProfiles && ( - - - - - - {availableProfiles && - Object.entries(availableProfiles) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([profileKey]) => ( - { - setSelectedProfile({ - executor: profileKey as BaseCodingAgent, - variant: null, - }); - }} - className={ - selectedProfile?.executor === profileKey - ? 'bg-accent' - : '' - } - > - {profileKey} - - ))} - - - )} -
+ {/* Step 2 & 3: Choose Profile and Variant */} + {availableProfiles && ( + + )} - {/* Step 3: Choose Variant (if available) */} -
-
- -
- {(() => { - const currentProfile = - availableProfiles?.[selectedProfile?.executor || '']; - const hasVariants = - currentProfile && Object.keys(currentProfile).length > 0; - - if (hasVariants && currentProfile) { - return ( - - - - - - {Object.entries(currentProfile).map(([variantLabel]) => ( - { - if (selectedProfile) { - setSelectedProfile({ - ...selectedProfile, - variant: variantLabel, - }); - } - }} - className={ - selectedProfile?.variant === variantLabel - ? 'bg-accent' - : '' - } - > - {variantLabel} - - ))} - - - ); - } - if (currentProfile) { - return ( - - ); - } - return ( - - ); - })()} -
- - {/* Step 4: Start Attempt */} + {/* Step 3: Start Attempt */}