using Microsoft.AspNetCore.Mvc; using Nexus.Api.Data; using Nexus.Api.Models; using Nexus.Api.Services; namespace Nexus.Api.Controllers; [ApiController] [Route("api/dashboard")] public class DashboardController(IDashboardService dashboardService, ITaskService taskService) : ControllerBase { [HttpGet("status")] public async Task GetStatus() => await dashboardService.GetStatusAsync(); [HttpGet("agents")] public async Task> GetAgents() => await dashboardService.GetAgentsAsync(); [HttpGet("operations")] public async Task> GetOperations( [FromQuery] int limit = 20, [FromQuery] string? agent = null) => await dashboardService.GetOperationsAsync(limit, agent); [HttpPost("chat/send")] public async Task SendChat([FromBody] ChatRequest request) { if (string.IsNullOrWhiteSpace(request.Message)) return new ChatResponse(false, null, "Message is required"); var agentId = string.IsNullOrWhiteSpace(request.AgentId) ? "iris" : request.AgentId.Trim(); return await dashboardService.SendChatAsync(agentId, request.Message.Trim()); } [HttpGet("chat/messages")] public async Task> GetMessages( [FromQuery] string? sessionKey, [FromQuery] int limit = 50, [FromQuery] int offset = 0) => await dashboardService.GetMessagesAsync(sessionKey, limit, offset); [HttpGet("queue")] public async Task> GetQueue(CancellationToken ct) => await dashboardService.GetQueueAsync(ct); [HttpDelete("queue/{id}")] public async Task DeleteQueueItem(string id, [FromQuery] string? source, CancellationToken ct) { var result = await dashboardService.DeleteQueueItemAsync(id, source, ct); return result.Outcome switch { QueueDeleteOutcome.Deleted => NoContent(), QueueDeleteOutcome.NotFound => NotFound(new { error = "Queue item not found" }), QueueDeleteOutcome.GatewayError => StatusCode(502, new { error = "Gateway could not delete cron job" }), QueueDeleteOutcome.TaskNotFound => NotFound(new { error = "Task not found" }), QueueDeleteOutcome.InvalidTaskId => BadRequest(new { error = "Invalid task id" }), _ => StatusCode(500, new { error = "Internal error" }) }; } [HttpPut("queue/{id}/priority")] public async Task ChangeQueuePriority(string id, CancellationToken ct) { var result = await dashboardService.CycleQueuePriorityAsync(id, ct); return result.Outcome switch { QueuePriorityOutcome.Ignored => Ok(new { status = "ignored", reason = "Cron job priorities are managed by the gateway" }), QueuePriorityOutcome.TaskNotFound => NotFound(new { error = "Task not found" }), QueuePriorityOutcome.InvalidTaskId => BadRequest(new { error = "Invalid task id" }), _ => Ok(new { status = "ok", priority = result.NewPriority }) }; } [HttpGet("agents/{id}/model")] public async Task> GetAgentModel(string id) { var info = await dashboardService.GetAgentModelAsync(id); return info is null ? NotFound(new { error = $"Agent '{id}' not found or gateway unreachable" }) : Ok(info); } [HttpPut("agents/{id}/model")] public async Task SetAgentModel(string id, [FromBody] SetModelRequest request) { if (string.IsNullOrWhiteSpace(request.Model)) return BadRequest(new { error = "Model is required" }); var ok = await dashboardService.SetAgentModelAsync(id, request.Model); return ok ? Ok(new { status = "ok", model = request.Model }) : StatusCode(502, new { error = "Gateway did not accept the change" }); } [HttpGet("agents/{id}/activity")] public async Task> GetAgentActivity(string id, [FromQuery] int limit = 5) => await dashboardService.GetAgentActivityAsync(id, limit); [HttpGet("models")] public ActionResult> GetAvailableModels() => Ok(dashboardService.GetAvailableModels()); // ── Task Endpoints ── [HttpGet("tasks")] public async Task> GetTasks(CancellationToken ct) { var tasks = await taskService.GetOpenAsync(ct); return tasks.Select(MapToDto).ToList(); } [HttpPost("tasks")] public async Task> CreateTask( [FromBody] CreateDashboardTaskRequest request, CancellationToken ct) { if (string.IsNullOrWhiteSpace(request.Title)) return BadRequest(new { error = "Title is required." }); var task = await taskService.CreateDashboardTaskAsync( request.Title, request.Detail, request.Source, request.Priority, request.AssignedTo, ct); return Created($"/api/dashboard/tasks/{task.Id}", MapToDto(task)); } [HttpPut("tasks/{id:guid}")] public async Task> UpdateTask( Guid id, [FromBody] UpdateDashboardTaskRequest request, CancellationToken ct) { var result = await taskService.UpdateDashboardTaskAsync( id, request.Title, request.Detail, request.Source, request.Priority, request.AssignedTo, ct); return result.Outcome switch { TaskOperationOutcome.NotFound => NotFound(new { error = "Task not found." }), _ => Ok(MapToDto(result.Task!)) }; } [HttpDelete("tasks/{id:guid}")] public async Task DeleteTask(Guid id, CancellationToken ct) { var result = await taskService.DeleteAsync(id, ct); return result.Outcome switch { TaskOperationOutcome.NotFound => NotFound(new { error = "Task not found." }), TaskOperationOutcome.InvalidState => StatusCode(403, new { error = "Only tasks in 'Done' or 'Backlog' state can be deleted." }), _ => NoContent() }; } [HttpPatch("tasks/{id:guid}/status")] public async Task> UpdateTaskStatus( Guid id, [FromBody] UpdateDashboardTaskStatusRequest request, CancellationToken ct) { var result = await taskService.UpdateStatusAsync(id, request.Status, ct); return result.Outcome switch { TaskOperationOutcome.InvalidState => BadRequest(new { error = $"Unsupported status: '{request.Status}'. Valid: {string.Join(", ", TaskStateHelper.AllStates)}" }), TaskOperationOutcome.NotFound => NotFound(new { error = "Task not found." }), _ => Ok(MapToDto(result.Task!)) }; } private static DashboardTaskDto MapToDto(WorkTask t) => new( t.Id, t.Title, t.Detail, t.Source, t.State, t.Priority, t.AssignedTo, t.CreatedAt, t.UpdatedAt); }