mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
feat: persist concurrent agents slider at project level
Add `default_concurrency` column to the projects table in the registry
database, allowing each project to remember its preferred concurrency
setting (1-5 agents). The value persists across page refreshes and
app restarts.
Backend changes:
- Add `default_concurrency` column to Project model in registry.py
- Add database migration for existing databases (ALTER TABLE)
- Add get/set_project_concurrency() CRUD functions
- Add ProjectSettingsUpdate schema with validation
- Add PATCH /{name}/settings endpoint in projects router
- Include default_concurrency in ProjectSummary/ProjectDetail responses
Frontend changes:
- Add default_concurrency to ProjectSummary TypeScript interface
- Add ProjectSettingsUpdate type and updateProjectSettings API function
- Add useUpdateProjectSettings React Query mutation hook
- Update AgentControl to accept defaultConcurrency prop
- Sync local state when project changes via useEffect
- Debounce slider changes (500ms) before saving to backend
- Pass defaultConcurrency from selectedProjectData in App.tsx
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ from ..schemas import (
|
||||
ProjectDetail,
|
||||
ProjectPrompts,
|
||||
ProjectPromptsUpdate,
|
||||
ProjectSettingsUpdate,
|
||||
ProjectStats,
|
||||
ProjectSummary,
|
||||
)
|
||||
@@ -63,13 +64,23 @@ def _get_registry_functions():
|
||||
sys.path.insert(0, str(root))
|
||||
|
||||
from registry import (
|
||||
get_project_concurrency,
|
||||
get_project_path,
|
||||
list_registered_projects,
|
||||
register_project,
|
||||
set_project_concurrency,
|
||||
unregister_project,
|
||||
validate_project_path,
|
||||
)
|
||||
return register_project, unregister_project, get_project_path, list_registered_projects, validate_project_path
|
||||
return (
|
||||
register_project,
|
||||
unregister_project,
|
||||
get_project_path,
|
||||
list_registered_projects,
|
||||
validate_project_path,
|
||||
get_project_concurrency,
|
||||
set_project_concurrency,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/projects", tags=["projects"])
|
||||
@@ -102,7 +113,8 @@ def get_project_stats(project_dir: Path) -> ProjectStats:
|
||||
async def list_projects():
|
||||
"""List all registered projects."""
|
||||
_init_imports()
|
||||
_, _, _, list_registered_projects, validate_project_path = _get_registry_functions()
|
||||
(_, _, _, list_registered_projects, validate_project_path,
|
||||
get_project_concurrency, _) = _get_registry_functions()
|
||||
|
||||
projects = list_registered_projects()
|
||||
result = []
|
||||
@@ -123,6 +135,7 @@ async def list_projects():
|
||||
path=info["path"],
|
||||
has_spec=has_spec,
|
||||
stats=stats,
|
||||
default_concurrency=info.get("default_concurrency", 3),
|
||||
))
|
||||
|
||||
return result
|
||||
@@ -132,7 +145,8 @@ async def list_projects():
|
||||
async def create_project(project: ProjectCreate):
|
||||
"""Create a new project at the specified path."""
|
||||
_init_imports()
|
||||
register_project, _, get_project_path, list_registered_projects, _ = _get_registry_functions()
|
||||
(register_project, _, get_project_path, list_registered_projects,
|
||||
_, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(project.name)
|
||||
project_path = Path(project.path).resolve()
|
||||
@@ -203,6 +217,7 @@ async def create_project(project: ProjectCreate):
|
||||
path=project_path.as_posix(),
|
||||
has_spec=False, # Just created, no spec yet
|
||||
stats=ProjectStats(passing=0, total=0, percentage=0.0),
|
||||
default_concurrency=3,
|
||||
)
|
||||
|
||||
|
||||
@@ -210,7 +225,7 @@ async def create_project(project: ProjectCreate):
|
||||
async def get_project(name: str):
|
||||
"""Get detailed information about a project."""
|
||||
_init_imports()
|
||||
_, _, get_project_path, _, _ = _get_registry_functions()
|
||||
(_, _, get_project_path, _, _, get_project_concurrency, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
@@ -231,6 +246,7 @@ async def get_project(name: str):
|
||||
has_spec=has_spec,
|
||||
stats=stats,
|
||||
prompts_dir=str(prompts_dir),
|
||||
default_concurrency=get_project_concurrency(name),
|
||||
)
|
||||
|
||||
|
||||
@@ -244,7 +260,7 @@ async def delete_project(name: str, delete_files: bool = False):
|
||||
delete_files: If True, also delete the project directory and files
|
||||
"""
|
||||
_init_imports()
|
||||
_, unregister_project, get_project_path, _, _ = _get_registry_functions()
|
||||
(_, unregister_project, get_project_path, _, _, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
@@ -280,7 +296,7 @@ async def delete_project(name: str, delete_files: bool = False):
|
||||
async def get_project_prompts(name: str):
|
||||
"""Get the content of project prompt files."""
|
||||
_init_imports()
|
||||
_, _, get_project_path, _, _ = _get_registry_functions()
|
||||
(_, _, get_project_path, _, _, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
@@ -313,7 +329,7 @@ async def get_project_prompts(name: str):
|
||||
async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
|
||||
"""Update project prompt files."""
|
||||
_init_imports()
|
||||
_, _, get_project_path, _, _ = _get_registry_functions()
|
||||
(_, _, get_project_path, _, _, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
@@ -343,7 +359,7 @@ async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
|
||||
async def get_project_stats_endpoint(name: str):
|
||||
"""Get current progress statistics for a project."""
|
||||
_init_imports()
|
||||
_, _, get_project_path, _, _ = _get_registry_functions()
|
||||
(_, _, get_project_path, _, _, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
@@ -355,3 +371,40 @@ async def get_project_stats_endpoint(name: str):
|
||||
raise HTTPException(status_code=404, detail="Project directory not found")
|
||||
|
||||
return get_project_stats(project_dir)
|
||||
|
||||
|
||||
@router.patch("/{name}/settings", response_model=ProjectDetail)
|
||||
async def update_project_settings(name: str, settings: ProjectSettingsUpdate):
|
||||
"""Update project-level settings (concurrency, etc.)."""
|
||||
_init_imports()
|
||||
(_, _, get_project_path, _, _, get_project_concurrency,
|
||||
set_project_concurrency) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
|
||||
if not project_dir:
|
||||
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
|
||||
|
||||
if not project_dir.exists():
|
||||
raise HTTPException(status_code=404, detail="Project directory not found")
|
||||
|
||||
# Update concurrency if provided
|
||||
if settings.default_concurrency is not None:
|
||||
success = set_project_concurrency(name, settings.default_concurrency)
|
||||
if not success:
|
||||
raise HTTPException(status_code=500, detail="Failed to update concurrency")
|
||||
|
||||
# Return updated project details
|
||||
has_spec = _check_spec_exists(project_dir)
|
||||
stats = get_project_stats(project_dir)
|
||||
prompts_dir = _get_project_prompts_dir(project_dir)
|
||||
|
||||
return ProjectDetail(
|
||||
name=name,
|
||||
path=project_dir.as_posix(),
|
||||
has_spec=has_spec,
|
||||
stats=stats,
|
||||
prompts_dir=str(prompts_dir),
|
||||
default_concurrency=get_project_concurrency(name),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user