From 6c50fef6fa9be5483079e0a5c848bb7483a602b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Tue, 13 Jan 2026 23:37:46 +0000 Subject: [PATCH] feat: add defaults to workspace: env, executor, agent --- .../internal/task/controller/workspace.go | 18 +- apps/backend/internal/task/dto/dto.go | 36 +- apps/backend/internal/task/dto/requests.go | 20 +- .../task/handlers/workspace_handlers.go | 78 ++-- apps/backend/internal/task/models/models.go | 16 +- .../internal/task/repository/memory.go | 3 - .../internal/task/repository/sqlite.go | 115 ++++-- apps/backend/internal/task/service/service.go | 68 ++-- apps/backend/pkg/api/v1/workspace.go | 32 +- apps/packages/ui/package.json | 10 +- apps/packages/ui/src/accordion.tsx | 81 ++++ apps/packages/ui/src/alert-dialog.tsx | 14 +- apps/packages/ui/src/alert.tsx | 72 ++++ apps/packages/ui/src/aspect-ratio.tsx | 11 + apps/packages/ui/src/avatar.tsx | 109 ++++++ apps/packages/ui/src/badge.tsx | 8 +- apps/packages/ui/src/breadcrumb.tsx | 10 +- apps/packages/ui/src/button-group.tsx | 83 ++++ apps/packages/ui/src/button.tsx | 24 +- apps/packages/ui/src/calendar.tsx | 216 +++++++++++ apps/packages/ui/src/card.tsx | 12 +- apps/packages/ui/src/carousel.tsx | 244 ++++++++++++ apps/packages/ui/src/chart.tsx | 356 ++++++++++++++++++ apps/packages/ui/src/checkbox.tsx | 33 ++ apps/packages/ui/src/collapsible.tsx | 2 +- apps/packages/ui/src/command.tsx | 269 +++++++------ apps/packages/ui/src/context-menu.tsx | 249 ++++++++++++ apps/packages/ui/src/dialog.tsx | 14 +- apps/packages/ui/src/drawer.tsx | 125 ++++++ apps/packages/ui/src/dropdown-menu.tsx | 20 +- apps/packages/ui/src/empty.tsx | 104 +++++ apps/packages/ui/src/field.tsx | 227 +++++++++++ apps/packages/ui/src/hover-card.tsx | 44 +++ apps/packages/ui/src/index.ts | 29 ++ apps/packages/ui/src/input-group.tsx | 147 ++++++++ apps/packages/ui/src/input-otp.tsx | 87 +++++ apps/packages/ui/src/input.tsx | 2 +- apps/packages/ui/src/item.tsx | 195 ++++++++++ apps/packages/ui/src/kbd.tsx | 26 ++ apps/packages/ui/src/label.tsx | 4 +- apps/packages/ui/src/menubar.tsx | 263 +++++++++++++ apps/packages/ui/src/navigation-menu.tsx | 161 ++++++++ apps/packages/ui/src/pagination.tsx | 132 +++++++ apps/packages/ui/src/popover.tsx | 100 +++-- apps/packages/ui/src/progress.tsx | 31 ++ apps/packages/ui/src/radio-group.tsx | 45 +++ apps/packages/ui/src/resizable.tsx | 2 +- apps/packages/ui/src/scroll-area.tsx | 55 +++ apps/packages/ui/src/select.tsx | 18 +- apps/packages/ui/src/separator.tsx | 2 +- apps/packages/ui/src/sheet.tsx | 14 +- apps/packages/ui/src/sidebar.tsx | 36 +- apps/packages/ui/src/slider.tsx | 6 +- apps/packages/ui/src/sonner.tsx | 49 +++ apps/packages/ui/src/spinner.tsx | 10 + apps/packages/ui/src/switch.tsx | 6 +- apps/packages/ui/src/table.tsx | 101 +++++ apps/packages/ui/src/tabs.tsx | 8 +- apps/packages/ui/src/textarea.tsx | 2 +- apps/packages/ui/src/toggle-group.tsx | 89 +++++ apps/packages/ui/src/toggle.tsx | 46 +++ apps/packages/ui/src/tooltip.tsx | 10 +- apps/web/app/actions/workspaces.ts | 10 +- apps/web/app/page.tsx | 3 + apps/web/app/settings/layout.tsx | 3 + .../workspace/workspace-edit-client.tsx | 243 +++++++++++- .../workspace/workspaces-page-client.tsx | 19 +- apps/web/components.json | 2 +- apps/web/components/kanban-board-header.tsx | 8 +- apps/web/components/task-create-dialog.tsx | 31 +- apps/web/lib/state/store.ts | 8 +- apps/web/lib/types/backend.ts | 3 + apps/web/lib/types/http.ts | 2 + apps/web/lib/ws/router.ts | 31 +- apps/web/package.json | 14 +- 75 files changed, 4373 insertions(+), 403 deletions(-) create mode 100644 apps/packages/ui/src/accordion.tsx create mode 100644 apps/packages/ui/src/alert.tsx create mode 100644 apps/packages/ui/src/aspect-ratio.tsx create mode 100644 apps/packages/ui/src/avatar.tsx create mode 100644 apps/packages/ui/src/button-group.tsx create mode 100644 apps/packages/ui/src/calendar.tsx create mode 100644 apps/packages/ui/src/carousel.tsx create mode 100644 apps/packages/ui/src/chart.tsx create mode 100644 apps/packages/ui/src/checkbox.tsx create mode 100644 apps/packages/ui/src/context-menu.tsx create mode 100644 apps/packages/ui/src/drawer.tsx create mode 100644 apps/packages/ui/src/empty.tsx create mode 100644 apps/packages/ui/src/field.tsx create mode 100644 apps/packages/ui/src/hover-card.tsx create mode 100644 apps/packages/ui/src/input-group.tsx create mode 100644 apps/packages/ui/src/input-otp.tsx create mode 100644 apps/packages/ui/src/item.tsx create mode 100644 apps/packages/ui/src/kbd.tsx create mode 100644 apps/packages/ui/src/menubar.tsx create mode 100644 apps/packages/ui/src/navigation-menu.tsx create mode 100644 apps/packages/ui/src/pagination.tsx create mode 100644 apps/packages/ui/src/progress.tsx create mode 100644 apps/packages/ui/src/radio-group.tsx create mode 100644 apps/packages/ui/src/scroll-area.tsx create mode 100644 apps/packages/ui/src/sonner.tsx create mode 100644 apps/packages/ui/src/spinner.tsx create mode 100644 apps/packages/ui/src/table.tsx create mode 100644 apps/packages/ui/src/toggle-group.tsx create mode 100644 apps/packages/ui/src/toggle.tsx diff --git a/apps/backend/internal/task/controller/workspace.go b/apps/backend/internal/task/controller/workspace.go index 0cdf2ac25f..6edf650ba1 100644 --- a/apps/backend/internal/task/controller/workspace.go +++ b/apps/backend/internal/task/controller/workspace.go @@ -40,10 +40,12 @@ func (c *WorkspaceController) GetWorkspace(ctx context.Context, req dto.GetWorks func (c *WorkspaceController) CreateWorkspace(ctx context.Context, req dto.CreateWorkspaceRequest) (dto.WorkspaceDTO, error) { workspace, err := c.service.CreateWorkspace(ctx, &service.CreateWorkspaceRequest{ - Name: req.Name, - Description: req.Description, - OwnerID: req.OwnerID, - DefaultExecutorID: req.DefaultExecutorID, + Name: req.Name, + Description: req.Description, + OwnerID: req.OwnerID, + DefaultExecutorID: req.DefaultExecutorID, + DefaultEnvironmentID: req.DefaultEnvironmentID, + DefaultAgentProfileID: req.DefaultAgentProfileID, }) if err != nil { return dto.WorkspaceDTO{}, err @@ -53,9 +55,11 @@ func (c *WorkspaceController) CreateWorkspace(ctx context.Context, req dto.Creat func (c *WorkspaceController) UpdateWorkspace(ctx context.Context, req dto.UpdateWorkspaceRequest) (dto.WorkspaceDTO, error) { workspace, err := c.service.UpdateWorkspace(ctx, req.ID, &service.UpdateWorkspaceRequest{ - Name: req.Name, - Description: req.Description, - DefaultExecutorID: req.DefaultExecutorID, + Name: req.Name, + Description: req.Description, + DefaultExecutorID: req.DefaultExecutorID, + DefaultEnvironmentID: req.DefaultEnvironmentID, + DefaultAgentProfileID: req.DefaultAgentProfileID, }) if err != nil { return dto.WorkspaceDTO{}, err diff --git a/apps/backend/internal/task/dto/dto.go b/apps/backend/internal/task/dto/dto.go index c446ba9770..4971116781 100644 --- a/apps/backend/internal/task/dto/dto.go +++ b/apps/backend/internal/task/dto/dto.go @@ -18,13 +18,15 @@ type BoardDTO struct { } type WorkspaceDTO struct { - ID string `json:"id"` - Name string `json:"name"` - Description *string `json:"description,omitempty"` - OwnerID string `json:"owner_id"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + OwnerID string `json:"owner_id"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } type ColumnDTO struct { @@ -213,19 +215,17 @@ func FromWorkspace(workspace *models.Workspace) WorkspaceDTO { if workspace.Description != "" { description = &workspace.Description } - var defaultExecutorID *string - if workspace.DefaultExecutorID != "" { - defaultExecutorID = &workspace.DefaultExecutorID - } return WorkspaceDTO{ - ID: workspace.ID, - Name: workspace.Name, - Description: description, - OwnerID: workspace.OwnerID, - DefaultExecutorID: defaultExecutorID, - CreatedAt: workspace.CreatedAt, - UpdatedAt: workspace.UpdatedAt, + ID: workspace.ID, + Name: workspace.Name, + Description: description, + OwnerID: workspace.OwnerID, + DefaultExecutorID: workspace.DefaultExecutorID, + DefaultEnvironmentID: workspace.DefaultEnvironmentID, + DefaultAgentProfileID: workspace.DefaultAgentProfileID, + CreatedAt: workspace.CreatedAt, + UpdatedAt: workspace.UpdatedAt, } } diff --git a/apps/backend/internal/task/dto/requests.go b/apps/backend/internal/task/dto/requests.go index 2889f93f33..48251eb8bb 100644 --- a/apps/backend/internal/task/dto/requests.go +++ b/apps/backend/internal/task/dto/requests.go @@ -16,17 +16,21 @@ type GetWorkspaceRequest struct { } type CreateWorkspaceRequest struct { - Name string - Description string - OwnerID string - DefaultExecutorID string + Name string + Description string + OwnerID string + DefaultExecutorID *string + DefaultEnvironmentID *string + DefaultAgentProfileID *string } type UpdateWorkspaceRequest struct { - ID string - Name *string - Description *string - DefaultExecutorID *string + ID string + Name *string + Description *string + DefaultExecutorID *string + DefaultEnvironmentID *string + DefaultAgentProfileID *string } type DeleteWorkspaceRequest struct { diff --git a/apps/backend/internal/task/handlers/workspace_handlers.go b/apps/backend/internal/task/handlers/workspace_handlers.go index b2142fa876..0f85e23e44 100644 --- a/apps/backend/internal/task/handlers/workspace_handlers.go +++ b/apps/backend/internal/task/handlers/workspace_handlers.go @@ -61,10 +61,12 @@ func (h *WorkspaceHandlers) httpListWorkspaces(c *gin.Context) { } type httpCreateWorkspaceRequest struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - DefaultExecutorID string `json:"default_executor_id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + OwnerID string `json:"owner_id,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } func (h *WorkspaceHandlers) httpCreateWorkspace(c *gin.Context) { @@ -78,10 +80,12 @@ func (h *WorkspaceHandlers) httpCreateWorkspace(c *gin.Context) { return } resp, err := h.controller.CreateWorkspace(c.Request.Context(), dto.CreateWorkspaceRequest{ - Name: body.Name, - Description: body.Description, - OwnerID: body.OwnerID, - DefaultExecutorID: body.DefaultExecutorID, + Name: body.Name, + Description: body.Description, + OwnerID: body.OwnerID, + DefaultExecutorID: body.DefaultExecutorID, + DefaultEnvironmentID: body.DefaultEnvironmentID, + DefaultAgentProfileID: body.DefaultAgentProfileID, }) if err != nil { handleNotFound(c, h.logger, err, "workspace not created") @@ -100,9 +104,11 @@ func (h *WorkspaceHandlers) httpGetWorkspace(c *gin.Context) { } type httpUpdateWorkspaceRequest struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } func (h *WorkspaceHandlers) httpUpdateWorkspace(c *gin.Context) { @@ -112,10 +118,12 @@ func (h *WorkspaceHandlers) httpUpdateWorkspace(c *gin.Context) { return } resp, err := h.controller.UpdateWorkspace(c.Request.Context(), dto.UpdateWorkspaceRequest{ - ID: c.Param("id"), - Name: body.Name, - Description: body.Description, - DefaultExecutorID: body.DefaultExecutorID, + ID: c.Param("id"), + Name: body.Name, + Description: body.Description, + DefaultExecutorID: body.DefaultExecutorID, + DefaultEnvironmentID: body.DefaultEnvironmentID, + DefaultAgentProfileID: body.DefaultAgentProfileID, }) if err != nil { handleNotFound(c, h.logger, err, "workspace not updated") @@ -145,10 +153,12 @@ func (h *WorkspaceHandlers) wsListWorkspaces(ctx context.Context, msg *ws.Messag } type wsCreateWorkspaceRequest struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - OwnerID string `json:"owner_id,omitempty"` - DefaultExecutorID string `json:"default_executor_id,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + OwnerID string `json:"owner_id,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } func (h *WorkspaceHandlers) wsCreateWorkspace(ctx context.Context, msg *ws.Message) (*ws.Message, error) { @@ -161,10 +171,12 @@ func (h *WorkspaceHandlers) wsCreateWorkspace(ctx context.Context, msg *ws.Messa } resp, err := h.controller.CreateWorkspace(ctx, dto.CreateWorkspaceRequest{ - Name: req.Name, - Description: req.Description, - OwnerID: req.OwnerID, - DefaultExecutorID: req.DefaultExecutorID, + Name: req.Name, + Description: req.Description, + OwnerID: req.OwnerID, + DefaultExecutorID: req.DefaultExecutorID, + DefaultEnvironmentID: req.DefaultEnvironmentID, + DefaultAgentProfileID: req.DefaultAgentProfileID, }) if err != nil { h.logger.Error("failed to create workspace", zap.Error(err)) @@ -194,10 +206,12 @@ func (h *WorkspaceHandlers) wsGetWorkspace(ctx context.Context, msg *ws.Message) } type wsUpdateWorkspaceRequest struct { - ID string `json:"id"` - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` + ID string `json:"id"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } func (h *WorkspaceHandlers) wsUpdateWorkspace(ctx context.Context, msg *ws.Message) (*ws.Message, error) { @@ -210,10 +224,12 @@ func (h *WorkspaceHandlers) wsUpdateWorkspace(ctx context.Context, msg *ws.Messa } resp, err := h.controller.UpdateWorkspace(ctx, dto.UpdateWorkspaceRequest{ - ID: req.ID, - Name: req.Name, - Description: req.Description, - DefaultExecutorID: req.DefaultExecutorID, + ID: req.ID, + Name: req.Name, + Description: req.Description, + DefaultExecutorID: req.DefaultExecutorID, + DefaultEnvironmentID: req.DefaultEnvironmentID, + DefaultAgentProfileID: req.DefaultAgentProfileID, }) if err != nil { h.logger.Error("failed to update workspace", zap.Error(err)) diff --git a/apps/backend/internal/task/models/models.go b/apps/backend/internal/task/models/models.go index 32b4924d3a..dae91a6a9b 100644 --- a/apps/backend/internal/task/models/models.go +++ b/apps/backend/internal/task/models/models.go @@ -38,13 +38,15 @@ type Board struct { // Workspace represents a workspace type Workspace struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - OwnerID string `json:"owner_id"` - DefaultExecutorID string `json:"default_executor_id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + OwnerID string `json:"owner_id"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // Column represents a column in a board diff --git a/apps/backend/internal/task/repository/memory.go b/apps/backend/internal/task/repository/memory.go index 1021821e71..72eebbcdde 100644 --- a/apps/backend/internal/task/repository/memory.go +++ b/apps/backend/internal/task/repository/memory.go @@ -114,9 +114,6 @@ func (r *MemoryRepository) CreateWorkspace(ctx context.Context, workspace *model if workspace.ID == "" { workspace.ID = uuid.New().String() } - if workspace.DefaultExecutorID == "" { - workspace.DefaultExecutorID = models.ExecutorIDLocalPC - } now := time.Now().UTC() workspace.CreatedAt = now workspace.UpdatedAt = now diff --git a/apps/backend/internal/task/repository/sqlite.go b/apps/backend/internal/task/repository/sqlite.go index 4c3f7685de..43fc5ea4b0 100644 --- a/apps/backend/internal/task/repository/sqlite.go +++ b/apps/backend/internal/task/repository/sqlite.go @@ -90,6 +90,8 @@ func (r *SQLiteRepository) initSchema() error { description TEXT DEFAULT '', owner_id TEXT DEFAULT '', default_executor_id TEXT DEFAULT '', + default_environment_id TEXT DEFAULT '', + default_agent_profile_id TEXT DEFAULT '', created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL ); @@ -271,6 +273,12 @@ func (r *SQLiteRepository) initSchema() error { if err := r.ensureColumn("workspaces", "default_executor_id", "TEXT DEFAULT ''"); err != nil { return err } + if err := r.ensureColumn("workspaces", "default_environment_id", "TEXT DEFAULT ''"); err != nil { + return err + } + if err := r.ensureColumn("workspaces", "default_agent_profile_id", "TEXT DEFAULT ''"); err != nil { + return err + } if err := r.ensureColumn("columns", "color", "TEXT NOT NULL DEFAULT ''"); err != nil { return err } @@ -377,9 +385,19 @@ func (r *SQLiteRepository) ensureDefaultWorkspace() error { workspaceDescription = "" } if _, err := r.db.ExecContext(ctx, ` - INSERT INTO workspaces (id, name, description, owner_id, default_executor_id, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?) - `, defaultID, workspaceName, workspaceDescription, "", models.ExecutorIDLocalPC, now, now); err != nil { + INSERT INTO workspaces ( + id, + name, + description, + owner_id, + default_executor_id, + default_environment_id, + default_agent_profile_id, + created_at, + updated_at + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, defaultID, workspaceName, workspaceDescription, "", nil, nil, nil, now, now); err != nil { return err } @@ -499,14 +517,6 @@ func (r *SQLiteRepository) ensureDefaultExecutorsAndEnvironments() error { } } - if _, err := r.db.ExecContext(ctx, ` - UPDATE workspaces - SET default_executor_id = ? - WHERE default_executor_id = '' OR default_executor_id IS NULL - `, models.ExecutorIDLocalPC); err != nil { - return err - } - var envCount int if err := r.db.QueryRowContext(ctx, "SELECT COUNT(1) FROM environments").Scan(&envCount); err != nil { return err @@ -578,17 +588,24 @@ func (r *SQLiteRepository) CreateWorkspace(ctx context.Context, workspace *model if workspace.ID == "" { workspace.ID = uuid.New().String() } - if workspace.DefaultExecutorID == "" { - workspace.DefaultExecutorID = models.ExecutorIDLocalPC - } now := time.Now().UTC() workspace.CreatedAt = now workspace.UpdatedAt = now _, err := r.db.ExecContext(ctx, ` - INSERT INTO workspaces (id, name, description, owner_id, default_executor_id, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?) - `, workspace.ID, workspace.Name, workspace.Description, workspace.OwnerID, workspace.DefaultExecutorID, workspace.CreatedAt, workspace.UpdatedAt) + INSERT INTO workspaces ( + id, + name, + description, + owner_id, + default_executor_id, + default_environment_id, + default_agent_profile_id, + created_at, + updated_at + ) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + `, workspace.ID, workspace.Name, workspace.Description, workspace.OwnerID, workspace.DefaultExecutorID, workspace.DefaultEnvironmentID, workspace.DefaultAgentProfileID, workspace.CreatedAt, workspace.UpdatedAt) return err } @@ -596,11 +613,33 @@ func (r *SQLiteRepository) CreateWorkspace(ctx context.Context, workspace *model // GetWorkspace retrieves a workspace by ID func (r *SQLiteRepository) GetWorkspace(ctx context.Context, id string) (*models.Workspace, error) { workspace := &models.Workspace{} + var defaultExecutorID sql.NullString + var defaultEnvironmentID sql.NullString + var defaultAgentProfileID sql.NullString err := r.db.QueryRowContext(ctx, ` - SELECT id, name, description, owner_id, default_executor_id, created_at, updated_at + SELECT id, name, description, owner_id, default_executor_id, default_environment_id, default_agent_profile_id, created_at, updated_at FROM workspaces WHERE id = ? - `, id).Scan(&workspace.ID, &workspace.Name, &workspace.Description, &workspace.OwnerID, &workspace.DefaultExecutorID, &workspace.CreatedAt, &workspace.UpdatedAt) + `, id).Scan( + &workspace.ID, + &workspace.Name, + &workspace.Description, + &workspace.OwnerID, + &defaultExecutorID, + &defaultEnvironmentID, + &defaultAgentProfileID, + &workspace.CreatedAt, + &workspace.UpdatedAt, + ) + if defaultExecutorID.Valid && defaultExecutorID.String != "" { + workspace.DefaultExecutorID = &defaultExecutorID.String + } + if defaultEnvironmentID.Valid && defaultEnvironmentID.String != "" { + workspace.DefaultEnvironmentID = &defaultEnvironmentID.String + } + if defaultAgentProfileID.Valid && defaultAgentProfileID.String != "" { + workspace.DefaultAgentProfileID = &defaultAgentProfileID.String + } if err == sql.ErrNoRows { return nil, fmt.Errorf("workspace not found: %s", id) @@ -613,8 +652,15 @@ func (r *SQLiteRepository) UpdateWorkspace(ctx context.Context, workspace *model workspace.UpdatedAt = time.Now().UTC() result, err := r.db.ExecContext(ctx, ` - UPDATE workspaces SET name = ?, description = ?, default_executor_id = ?, updated_at = ? WHERE id = ? - `, workspace.Name, workspace.Description, workspace.DefaultExecutorID, workspace.UpdatedAt, workspace.ID) + UPDATE workspaces + SET name = ?, + description = ?, + default_executor_id = ?, + default_environment_id = ?, + default_agent_profile_id = ?, + updated_at = ? + WHERE id = ? + `, workspace.Name, workspace.Description, workspace.DefaultExecutorID, workspace.DefaultEnvironmentID, workspace.DefaultAgentProfileID, workspace.UpdatedAt, workspace.ID) if err != nil { return err } @@ -643,7 +689,8 @@ func (r *SQLiteRepository) DeleteWorkspace(ctx context.Context, id string) error // ListWorkspaces returns all workspaces func (r *SQLiteRepository) ListWorkspaces(ctx context.Context) ([]*models.Workspace, error) { rows, err := r.db.QueryContext(ctx, ` - SELECT id, name, description, owner_id, default_executor_id, created_at, updated_at FROM workspaces ORDER BY created_at DESC + SELECT id, name, description, owner_id, default_executor_id, default_environment_id, default_agent_profile_id, created_at, updated_at + FROM workspaces ORDER BY created_at DESC `) if err != nil { return nil, err @@ -653,9 +700,31 @@ func (r *SQLiteRepository) ListWorkspaces(ctx context.Context) ([]*models.Worksp var result []*models.Workspace for rows.Next() { workspace := &models.Workspace{} - if err := rows.Scan(&workspace.ID, &workspace.Name, &workspace.Description, &workspace.OwnerID, &workspace.DefaultExecutorID, &workspace.CreatedAt, &workspace.UpdatedAt); err != nil { + var defaultExecutorID sql.NullString + var defaultEnvironmentID sql.NullString + var defaultAgentProfileID sql.NullString + if err := rows.Scan( + &workspace.ID, + &workspace.Name, + &workspace.Description, + &workspace.OwnerID, + &defaultExecutorID, + &defaultEnvironmentID, + &defaultAgentProfileID, + &workspace.CreatedAt, + &workspace.UpdatedAt, + ); err != nil { return nil, err } + if defaultExecutorID.Valid && defaultExecutorID.String != "" { + workspace.DefaultExecutorID = &defaultExecutorID.String + } + if defaultEnvironmentID.Valid && defaultEnvironmentID.String != "" { + workspace.DefaultEnvironmentID = &defaultEnvironmentID.String + } + if defaultAgentProfileID.Valid && defaultAgentProfileID.String != "" { + workspace.DefaultAgentProfileID = &defaultAgentProfileID.String + } result = append(result, workspace) } return result, rows.Err() diff --git a/apps/backend/internal/task/service/service.go b/apps/backend/internal/task/service/service.go index d6879fbd82..6630e89904 100644 --- a/apps/backend/internal/task/service/service.go +++ b/apps/backend/internal/task/service/service.go @@ -93,17 +93,21 @@ type UpdateBoardRequest struct { // CreateWorkspaceRequest contains the data for creating a new workspace type CreateWorkspaceRequest struct { - Name string `json:"name"` - Description string `json:"description"` - OwnerID string `json:"owner_id"` - DefaultExecutorID string `json:"default_executor_id"` + Name string `json:"name"` + Description string `json:"description"` + OwnerID string `json:"owner_id"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } // UpdateWorkspaceRequest contains the data for updating a workspace type UpdateWorkspaceRequest struct { - Name *string `json:"name,omitempty"` - Description *string `json:"description,omitempty"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` + Name *string `json:"name,omitempty"` + Description *string `json:"description,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } // CreateColumnRequest contains the data for creating a new column @@ -464,16 +468,14 @@ func (s *Service) MoveTask(ctx context.Context, id string, boardID string, colum // CreateWorkspace creates a new workspace func (s *Service) CreateWorkspace(ctx context.Context, req *CreateWorkspaceRequest) (*models.Workspace, error) { - defaultExecutorID := req.DefaultExecutorID - if defaultExecutorID == "" { - defaultExecutorID = models.ExecutorIDLocalPC - } workspace := &models.Workspace{ - ID: uuid.New().String(), - Name: req.Name, - Description: req.Description, - OwnerID: req.OwnerID, - DefaultExecutorID: defaultExecutorID, + ID: uuid.New().String(), + Name: req.Name, + Description: req.Description, + OwnerID: req.OwnerID, + DefaultExecutorID: normalizeOptionalID(req.DefaultExecutorID), + DefaultEnvironmentID: normalizeOptionalID(req.DefaultEnvironmentID), + DefaultAgentProfileID: normalizeOptionalID(req.DefaultAgentProfileID), } if err := s.repo.CreateWorkspace(ctx, workspace); err != nil { @@ -505,7 +507,13 @@ func (s *Service) UpdateWorkspace(ctx context.Context, id string, req *UpdateWor workspace.Description = *req.Description } if req.DefaultExecutorID != nil { - workspace.DefaultExecutorID = *req.DefaultExecutorID + workspace.DefaultExecutorID = normalizeOptionalID(req.DefaultExecutorID) + } + if req.DefaultEnvironmentID != nil { + workspace.DefaultEnvironmentID = normalizeOptionalID(req.DefaultEnvironmentID) + } + if req.DefaultAgentProfileID != nil { + workspace.DefaultAgentProfileID = normalizeOptionalID(req.DefaultAgentProfileID) } workspace.UpdatedAt = time.Now().UTC() @@ -534,6 +542,17 @@ func (s *Service) DeleteWorkspace(ctx context.Context, id string) error { return nil } +func normalizeOptionalID(value *string) *string { + if value == nil { + return nil + } + trimmed := strings.TrimSpace(*value) + if trimmed == "" { + return nil + } + return &trimmed +} + // ListWorkspaces returns all workspaces func (s *Service) ListWorkspaces(ctx context.Context) ([]*models.Workspace, error) { return s.repo.ListWorkspaces(ctx) @@ -1076,12 +1095,15 @@ func (s *Service) publishWorkspaceEvent(ctx context.Context, eventType string, w } data := map[string]interface{}{ - "id": workspace.ID, - "name": workspace.Name, - "description": workspace.Description, - "owner_id": workspace.OwnerID, - "created_at": workspace.CreatedAt.Format(time.RFC3339), - "updated_at": workspace.UpdatedAt.Format(time.RFC3339), + "id": workspace.ID, + "name": workspace.Name, + "description": workspace.Description, + "owner_id": workspace.OwnerID, + "default_executor_id": workspace.DefaultExecutorID, + "default_environment_id": workspace.DefaultEnvironmentID, + "default_agent_profile_id": workspace.DefaultAgentProfileID, + "created_at": workspace.CreatedAt.Format(time.RFC3339), + "updated_at": workspace.UpdatedAt.Format(time.RFC3339), } event := bus.NewEvent(eventType, "task-service", data) diff --git a/apps/backend/pkg/api/v1/workspace.go b/apps/backend/pkg/api/v1/workspace.go index 9e005f7ae5..ac01385a7a 100644 --- a/apps/backend/pkg/api/v1/workspace.go +++ b/apps/backend/pkg/api/v1/workspace.go @@ -4,25 +4,31 @@ import "time" // Workspace represents a workspace type Workspace struct { - ID string `json:"id"` - Name string `json:"name"` - Description *string `json:"description,omitempty"` - OwnerID string `json:"owner_id"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + OwnerID string `json:"owner_id"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } // CreateWorkspaceRequest for creating a new workspace type CreateWorkspaceRequest struct { - Name string `json:"name" binding:"required,max=255"` - Description *string `json:"description,omitempty"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` + Name string `json:"name" binding:"required,max=255"` + Description *string `json:"description,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } // UpdateWorkspaceRequest for updating an existing workspace type UpdateWorkspaceRequest struct { - Name *string `json:"name,omitempty" binding:"omitempty,max=255"` - Description *string `json:"description,omitempty"` - DefaultExecutorID *string `json:"default_executor_id,omitempty"` + Name *string `json:"name,omitempty" binding:"omitempty,max=255"` + Description *string `json:"description,omitempty"` + DefaultExecutorID *string `json:"default_executor_id,omitempty"` + DefaultEnvironmentID *string `json:"default_environment_id,omitempty"` + DefaultAgentProfileID *string `json:"default_agent_profile_id,omitempty"` } diff --git a/apps/packages/ui/package.json b/apps/packages/ui/package.json index 6df2fdf01e..60f1d77298 100644 --- a/apps/packages/ui/package.json +++ b/apps/packages/ui/package.json @@ -25,8 +25,16 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react-day-picker": "^9.13.0", "react-resizable-panels": "^2.1.9", - "tailwind-merge": "^2.6.0" + "recharts": "2.15.4", + "sonner": "^2.0.7", + "tailwind-merge": "^2.6.0", + "vaul": "^1.1.2" }, "peerDependencies": { "react": "^19.0.0", diff --git a/apps/packages/ui/src/accordion.tsx b/apps/packages/ui/src/accordion.tsx new file mode 100644 index 0000000000..81427895fe --- /dev/null +++ b/apps/packages/ui/src/accordion.tsx @@ -0,0 +1,81 @@ +"use client" + +import * as React from "react" +import { Accordion as AccordionPrimitive } from "radix-ui" + +import { cn } from "./lib/utils" +import { IconChevronDown, IconChevronUp } from "@tabler/icons-react" + +function Accordion({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
+ {children} +
+
+ ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/apps/packages/ui/src/alert-dialog.tsx b/apps/packages/ui/src/alert-dialog.tsx index fa5cfee2e7..3835fa48ac 100644 --- a/apps/packages/ui/src/alert-dialog.tsx +++ b/apps/packages/ui/src/alert-dialog.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" +import { AlertDialog as AlertDialogPrimitive } from "radix-ui" import { cn } from "./lib/utils" import { Button } from "./button" @@ -35,7 +35,7 @@ function AlertDialogOverlay({ return ( ) @@ -55,7 +55,7 @@ function AlertDialogContent({ data-slot="alert-dialog-content" data-size={size} className={cn( - "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-lg group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", + "data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-3 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", className )} {...props} @@ -71,7 +71,7 @@ function AlertDialogHeader({ return (
) @@ -100,7 +100,7 @@ function AlertDialogMedia({ return (
) @@ -113,7 +113,7 @@ function AlertDialogTitle({ return ( ) @@ -126,7 +126,7 @@ function AlertDialogDescription({ return ( ) diff --git a/apps/packages/ui/src/alert.tsx b/apps/packages/ui/src/alert.tsx new file mode 100644 index 0000000000..aecc9e641d --- /dev/null +++ b/apps/packages/ui/src/alert.tsx @@ -0,0 +1,72 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "./lib/utils" + +const alertVariants = cva("grid gap-0.5 rounded-lg border px-2 py-1.5 text-left text-xs/relaxed has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-1.5 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-3.5 w-full relative group/alert", { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: "text-destructive bg-card *:data-[slot=alert-description]:text-destructive/90 *:[svg]:text-current", + }, + }, + defaultVariants: { + variant: "default", + }, +}) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3", + className + )} + {...props} + /> + ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertAction({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +export { Alert, AlertTitle, AlertDescription, AlertAction } diff --git a/apps/packages/ui/src/aspect-ratio.tsx b/apps/packages/ui/src/aspect-ratio.tsx new file mode 100644 index 0000000000..57e38fa96f --- /dev/null +++ b/apps/packages/ui/src/aspect-ratio.tsx @@ -0,0 +1,11 @@ +"use client" + +import { AspectRatio as AspectRatioPrimitive } from "radix-ui" + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/apps/packages/ui/src/avatar.tsx b/apps/packages/ui/src/avatar.tsx new file mode 100644 index 0000000000..d5700e7c5b --- /dev/null +++ b/apps/packages/ui/src/avatar.tsx @@ -0,0 +1,109 @@ +"use client" + +import * as React from "react" +import { Avatar as AvatarPrimitive } from "radix-ui" + +import { cn } from "./lib/utils" + +function Avatar({ + className, + size = "default", + ...props +}: React.ComponentProps & { + size?: "default" | "sm" | "lg" +}) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) { + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + className + )} + {...props} + /> + ) +} + +function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AvatarGroupCount({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2", className)} + {...props} + /> + ) +} + +export { + Avatar, + AvatarImage, + AvatarFallback, + AvatarGroup, + AvatarGroupCount, + AvatarBadge, +} diff --git a/apps/packages/ui/src/badge.tsx b/apps/packages/ui/src/badge.tsx index 7598294334..f5db89759d 100644 --- a/apps/packages/ui/src/badge.tsx +++ b/apps/packages/ui/src/badge.tsx @@ -1,18 +1,18 @@ import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" -import { Slot } from "@radix-ui/react-slot" +import { Slot } from "radix-ui" import { cn } from "./lib/utils" const badgeVariants = cva( - "h-5 gap-1 rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-3! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge", + "h-5 gap-1 rounded-full border border-transparent px-2 py-0.5 text-[0.625rem] font-medium transition-all has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&>svg]:size-2.5! inline-flex items-center justify-center w-fit whitespace-nowrap shrink-0 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-colors overflow-hidden group/badge", { variants: { variant: { default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80", secondary: "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80", destructive: "bg-destructive/10 [a]:hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 text-destructive dark:bg-destructive/20", - outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground", + outline: "border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30", ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50", link: "text-primary underline-offset-4 hover:underline", }, @@ -30,7 +30,7 @@ function Badge({ ...props }: React.ComponentProps<"span"> & VariantProps & { asChild?: boolean }) { - const Comp = asChild ? Slot : "span" + const Comp = asChild ? Slot.Root : "span" return ( ) {
    ) { return (
  1. ) @@ -45,7 +45,7 @@ function BreadcrumbLink({ }: React.ComponentProps<"a"> & { asChild?: boolean }) { - const Comp = asChild ? Slot : "a" + const Comp = asChild ? Slot.Root : "a" return (