mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
feat: Add global settings modal and simplify agent controls
Adds a settings system for global configuration with YOLO mode toggle and
model selection. Simplifies the agent control UI by removing redundant
status indicator and pause functionality.
## Settings System
- New SettingsModal with YOLO mode toggle and model selection
- Settings persisted in SQLite (registry.db) - shared across all projects
- Models fetched from API endpoint (/api/settings/models)
- Single source of truth for models in registry.py - easy to add new models
- Optimistic UI updates with rollback on error
## Agent Control Simplification
- Removed StatusIndicator ("STOPPED"/"RUNNING" label) - redundant
- Removed Pause/Resume buttons - just Start/Stop toggle now
- Start button shows flame icon with fiery gradient when YOLO mode enabled
## Code Review Fixes
- Added focus trap to SettingsModal for accessibility
- Fixed YOLO button color contrast (WCAG AA compliance)
- Added model validation to AgentStartRequest schema
- Added model to AgentStatus response
- Added aria-labels to all icon-only buttons
- Added role="radiogroup" to model selection
- Added loading indicator during settings save
- Added SQLite timeout (30s) and retry logic with exponential backoff
- Added thread-safe database engine initialization
- Added orphaned lock file cleanup on server startup
## Files Changed
- registry.py: Model config, Settings CRUD, SQLite improvements
- server/routers/settings.py: New settings API
- server/schemas.py: Settings schemas with validation
- server/services/process_manager.py: Model param, orphan cleanup
- ui/src/components/SettingsModal.tsx: New modal component
- ui/src/components/AgentControl.tsx: Simplified to Start/Stop only
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from .assistant_chat import router as assistant_chat_router
|
||||
from .features import router as features_router
|
||||
from .filesystem import router as filesystem_router
|
||||
from .projects import router as projects_router
|
||||
from .settings import router as settings_router
|
||||
from .spec_creation import router as spec_creation_router
|
||||
|
||||
__all__ = [
|
||||
@@ -19,4 +20,5 @@ __all__ = [
|
||||
"spec_creation_router",
|
||||
"filesystem_router",
|
||||
"assistant_chat_router",
|
||||
"settings_router",
|
||||
]
|
||||
|
||||
@@ -26,6 +26,21 @@ def _get_project_path(project_name: str) -> Path:
|
||||
return get_project_path(project_name)
|
||||
|
||||
|
||||
def _get_settings_defaults() -> tuple[bool, str]:
|
||||
"""Get YOLO mode and model defaults from global settings."""
|
||||
import sys
|
||||
root = Path(__file__).parent.parent.parent
|
||||
if str(root) not in sys.path:
|
||||
sys.path.insert(0, str(root))
|
||||
|
||||
from registry import DEFAULT_MODEL, get_all_settings
|
||||
|
||||
settings = get_all_settings()
|
||||
yolo_mode = (settings.get("yolo_mode") or "false").lower() == "true"
|
||||
model = settings.get("model", DEFAULT_MODEL)
|
||||
return yolo_mode, model
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
|
||||
|
||||
# Root directory for process manager
|
||||
@@ -69,6 +84,7 @@ async def get_agent_status(project_name: str):
|
||||
pid=manager.pid,
|
||||
started_at=manager.started_at,
|
||||
yolo_mode=manager.yolo_mode,
|
||||
model=manager.model,
|
||||
)
|
||||
|
||||
|
||||
@@ -80,7 +96,12 @@ async def start_agent(
|
||||
"""Start the agent for a project."""
|
||||
manager = get_project_manager(project_name)
|
||||
|
||||
success, message = await manager.start(yolo_mode=request.yolo_mode)
|
||||
# Get defaults from global settings if not provided in request
|
||||
default_yolo, default_model = _get_settings_defaults()
|
||||
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
|
||||
model = request.model if request.model else default_model
|
||||
|
||||
success, message = await manager.start(yolo_mode=yolo_mode, model=model)
|
||||
|
||||
return AgentActionResponse(
|
||||
success=success,
|
||||
|
||||
75
server/routers/settings.py
Normal file
75
server/routers/settings.py
Normal file
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Settings Router
|
||||
===============
|
||||
|
||||
API endpoints for global settings management.
|
||||
Settings are stored in the registry database and shared across all projects.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
from ..schemas import ModelInfo, ModelsResponse, SettingsResponse, SettingsUpdate
|
||||
|
||||
# Add root to path for registry import
|
||||
ROOT_DIR = Path(__file__).parent.parent.parent
|
||||
if str(ROOT_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT_DIR))
|
||||
|
||||
from registry import (
|
||||
AVAILABLE_MODELS,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_YOLO_MODE,
|
||||
get_all_settings,
|
||||
set_setting,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/api/settings", tags=["settings"])
|
||||
|
||||
|
||||
def _parse_yolo_mode(value: str | None) -> bool:
|
||||
"""Parse YOLO mode string to boolean."""
|
||||
return (value or "false").lower() == "true"
|
||||
|
||||
|
||||
@router.get("/models", response_model=ModelsResponse)
|
||||
async def get_available_models():
|
||||
"""Get list of available models.
|
||||
|
||||
Frontend should call this to get the current list of models
|
||||
instead of hardcoding them.
|
||||
"""
|
||||
return ModelsResponse(
|
||||
models=[ModelInfo(id=m["id"], name=m["name"]) for m in AVAILABLE_MODELS],
|
||||
default=DEFAULT_MODEL,
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=SettingsResponse)
|
||||
async def get_settings():
|
||||
"""Get current global settings."""
|
||||
all_settings = get_all_settings()
|
||||
|
||||
return SettingsResponse(
|
||||
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),
|
||||
model=all_settings.get("model", DEFAULT_MODEL),
|
||||
)
|
||||
|
||||
|
||||
@router.patch("", response_model=SettingsResponse)
|
||||
async def update_settings(update: SettingsUpdate):
|
||||
"""Update global settings."""
|
||||
if update.yolo_mode is not None:
|
||||
set_setting("yolo_mode", "true" if update.yolo_mode else "false")
|
||||
|
||||
if update.model is not None:
|
||||
set_setting("model", update.model)
|
||||
|
||||
# Return updated settings
|
||||
all_settings = get_all_settings()
|
||||
return SettingsResponse(
|
||||
yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")),
|
||||
model=all_settings.get("model", DEFAULT_MODEL),
|
||||
)
|
||||
Reference in New Issue
Block a user