From 30575c6f77ac8e9964f235ab89ab9d9930e30af1 Mon Sep 17 00:00:00 2001 From: jean-philippe Date: Sun, 26 Oct 2025 20:28:45 -0400 Subject: [PATCH] feat: Integrate backend list agents endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend team successfully fixed Swagger conflicts and implemented simple GET endpoints. Frontend now integrates with GET /api/agents to list all agents. Changes: - agent_endpoint.dart: - Added fromInt() methods to all enums (AgentType, AgentStatus, ModelProviderType) - Updated AgentDto.fromJson() to handle integer enum values from backend - Added listAgents() method using HTTP GET /api/agents - Added imports: dart:async, dart:convert, dart:io, package:http - agents_page.dart: - Updated _loadAgents() to call listAgents() API method - Removed placeholder delay, now uses real data from backend - Removed unused getwidget import Backend Integration: ✅ Backend returns 5 test agents (seeded successfully) ✅ Enums transmitted as integers (CodeGenerator=0, Active=0, etc.) ✅ Frontend properly parses integer enums to Dart enum types ✅ GET /api/agents endpoint working and tested ✅ Full CRUD cycle now functional Testing: - Flutter analyze: 0 errors, 0 warnings - Backend health check: ✅ passing - List endpoint: ✅ returns 5 agents - App running: http://localhost:8080 Phase 2 Complete: Frontend can now display agents from backend! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../lib/api/endpoints/agent_endpoint.dart | 119 +++++++++++++++++- FRONTEND/lib/pages/agents_page.dart | 27 ++-- 2 files changed, 135 insertions(+), 11 deletions(-) diff --git a/FRONTEND/lib/api/endpoints/agent_endpoint.dart b/FRONTEND/lib/api/endpoints/agent_endpoint.dart index 3be646a..4018f82 100644 --- a/FRONTEND/lib/api/endpoints/agent_endpoint.dart +++ b/FRONTEND/lib/api/endpoints/agent_endpoint.dart @@ -1,6 +1,12 @@ /// Agent management endpoints for CQRS API library; +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:http/http.dart' as http; + import '../client.dart'; import '../types.dart'; @@ -25,6 +31,15 @@ enum AgentType { orElse: () => AgentType.custom, ); } + + /// Convert from integer value (backend enum representation) + /// Backend: CodeGenerator=0, CodeReviewer=1, Debugger=2, Documenter=3, Custom=4 + static AgentType fromInt(int value) { + if (value >= 0 && value < AgentType.values.length) { + return AgentType.values[value]; + } + return AgentType.custom; + } } /// Represents the current status of an agent @@ -42,6 +57,15 @@ enum AgentStatus { orElse: () => AgentStatus.inactive, ); } + + /// Convert from integer value (backend enum representation) + /// Backend: Active=0, Inactive=1, Error=2 + static AgentStatus fromInt(int value) { + if (value >= 0 && value < AgentStatus.values.length) { + return AgentStatus.values[value]; + } + return AgentStatus.inactive; + } } /// Specifies the type of model provider (cloud API or local endpoint) @@ -59,6 +83,15 @@ enum ModelProviderType { orElse: () => ModelProviderType.custom, ); } + + /// Convert from integer value (backend enum representation) + /// Backend: CloudApi=0, LocalEndpoint=1, Custom=2 + static ModelProviderType fromInt(int value) { + if (value >= 0 && value < ModelProviderType.values.length) { + return ModelProviderType.values[value]; + } + return ModelProviderType.custom; + } } // ============================================================================= @@ -239,21 +272,40 @@ class AgentDto { }); factory AgentDto.fromJson(Map json) { + // Helper to parse enum from either int or string + AgentType parseAgentType(Object? value) { + if (value is int) return AgentType.fromInt(value); + if (value is String) return AgentType.fromString(value); + return AgentType.custom; + } + + AgentStatus parseAgentStatus(Object? value) { + if (value is int) return AgentStatus.fromInt(value); + if (value is String) return AgentStatus.fromString(value); + return AgentStatus.inactive; + } + + ModelProviderType parseProviderType(Object? value) { + if (value is int) return ModelProviderType.fromInt(value); + if (value is String) return ModelProviderType.fromString(value); + return ModelProviderType.custom; + } + return AgentDto( id: json['id'] as String, name: json['name'] as String, description: json['description'] as String, - type: AgentType.fromString(json['type'] as String), + type: parseAgentType(json['type']), modelProvider: json['modelProvider'] as String, modelName: json['modelName'] as String, - providerType: ModelProviderType.fromString(json['providerType'] as String), + providerType: parseProviderType(json['providerType']), modelEndpoint: json['modelEndpoint'] as String?, temperature: (json['temperature'] as num).toDouble(), maxTokens: json['maxTokens'] as int, systemPrompt: json['systemPrompt'] as String, enableMemory: json['enableMemory'] as bool, conversationWindowSize: json['conversationWindowSize'] as int, - status: AgentStatus.fromString(json['status'] as String), + status: parseAgentStatus(json['status']), createdAt: DateTime.parse(json['createdAt'] as String), updatedAt: DateTime.parse(json['updatedAt'] as String), ); @@ -361,4 +413,65 @@ extension AgentEndpoint on CqrsApiClient { fromJson: (json) => AgentDto.fromJson(json as Map), ); } + + /// List all agents + /// + /// Returns a list of all active agents from the backend. + /// Backend endpoint: GET /api/agents + /// + /// Example: + /// ```dart + /// final result = await client.listAgents(); + /// + /// result.when( + /// success: (agents) => print('Found ${agents.length} agents'), + /// error: (error) => print('Error: ${error.message}'), + /// ); + /// ``` + Future>> listAgents() async { + try { + final Uri url = Uri.parse('${config.baseUrl}/api/agents'); + final http.Response response = await http + .get(url, headers: config.defaultHeaders) + .timeout(config.timeout); + + if (response.statusCode >= 200 && response.statusCode < 300) { + final Object? jsonData = jsonDecode(response.body); + if (jsonData is! List) { + return ApiError>(ApiErrorInfo( + message: 'Expected array response, got ${jsonData.runtimeType}', + type: ApiErrorType.serialization, + )); + } + + final List agents = jsonData + .map((item) => AgentDto.fromJson(item as Map)) + .toList(); + + return ApiSuccess>(agents); + } + + // Handle error responses + return ApiError>(ApiErrorInfo( + message: 'Failed to load agents', + type: ApiErrorType.http, + statusCode: response.statusCode, + )); + } on TimeoutException { + return ApiError>(ApiErrorInfo( + message: 'Request timed out', + type: ApiErrorType.timeout, + )); + } on SocketException { + return ApiError>(ApiErrorInfo( + message: 'No internet connection', + type: ApiErrorType.network, + )); + } catch (e) { + return ApiError>(ApiErrorInfo( + message: 'Unexpected error: $e', + type: ApiErrorType.unknown, + )); + } + } } diff --git a/FRONTEND/lib/pages/agents_page.dart b/FRONTEND/lib/pages/agents_page.dart index dfff0fa..f6bec2b 100644 --- a/FRONTEND/lib/pages/agents_page.dart +++ b/FRONTEND/lib/pages/agents_page.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:iconsax/iconsax.dart'; import 'package:animate_do/animate_do.dart'; -import 'package:getwidget/getwidget.dart'; import '../api/api.dart'; import '../dialogs/create_agent_dialog.dart'; @@ -43,14 +42,26 @@ class _AgentsPageState extends State { _errorMessage = null; }); - // TODO: Implement list agents endpoint when available - // For now, show empty state - await Future.delayed(const Duration(milliseconds: 500)); + final Result> result = await _apiClient.listAgents(); - setState(() { - _agents = []; - _isLoading = false; - }); + result.when( + success: (List agents) { + if (mounted) { + setState(() { + _agents = agents; + _isLoading = false; + }); + } + }, + error: (ApiErrorInfo error) { + if (mounted) { + setState(() { + _errorMessage = error.message; + _isLoading = false; + }); + } + }, + ); } Future _createAgent(CreateAgentCommand command) async {