/// Conversation management endpoints for CQRS API library; import 'dart:convert'; import 'dart:io'; import 'dart:async'; import 'package:http/http.dart' as http; import '../client.dart'; import '../types.dart'; // ============================================================================= // Commands // ============================================================================= /// Command to create a new conversation class CreateConversationCommand implements Serializable { final String title; final String? summary; const CreateConversationCommand({ required this.title, this.summary, }); @override Map toJson() => { 'title': title, if (summary != null) 'summary': summary, }; } // ============================================================================= // Queries // ============================================================================= /// Query to get a single conversation by ID class GetConversationQuery implements Serializable { final String id; const GetConversationQuery({required this.id}); @override Map toJson() => {'id': id}; } // ============================================================================= // DTOs // ============================================================================= /// Response when creating a conversation (returns only ID) class CreateConversationResult { final String id; const CreateConversationResult({required this.id}); factory CreateConversationResult.fromJson(Map json) { return CreateConversationResult( id: json['id'] as String, ); } Map toJson() => {'id': id}; } /// Conversation message DTO class ConversationMessageDto { final String id; final String role; final String content; final DateTime timestamp; const ConversationMessageDto({ required this.id, required this.role, required this.content, required this.timestamp, }); factory ConversationMessageDto.fromJson(Map json) { return ConversationMessageDto( id: json['id'] as String, role: json['role'] as String, content: json['content'] as String, timestamp: DateTime.parse(json['timestamp'] as String), ); } Map toJson() => { 'id': id, 'role': role, 'content': content, 'timestamp': timestamp.toIso8601String(), }; } /// Full conversation details with messages and executions class ConversationDto { final String id; final String title; final String? summary; final DateTime startedAt; final DateTime lastMessageAt; final int messageCount; final bool isActive; final int executionCount; final List messages; const ConversationDto({ required this.id, required this.title, this.summary, required this.startedAt, required this.lastMessageAt, required this.messageCount, required this.isActive, required this.executionCount, required this.messages, }); factory ConversationDto.fromJson(Map json) { final List messagesList = json['messages'] as List? ?? []; final List messages = messagesList .cast>() .map((Map m) => ConversationMessageDto.fromJson(m)) .toList(); return ConversationDto( id: json['id'] as String, title: json['title'] as String, summary: json['summary'] as String?, startedAt: DateTime.parse(json['startedAt'] as String), lastMessageAt: DateTime.parse(json['lastMessageAt'] as String), messageCount: json['messageCount'] as int, isActive: json['isActive'] as bool, executionCount: json['executionCount'] as int, messages: messages, ); } Map toJson() => { 'id': id, 'title': title, 'summary': summary, 'startedAt': startedAt.toIso8601String(), 'lastMessageAt': lastMessageAt.toIso8601String(), 'messageCount': messageCount, 'isActive': isActive, 'executionCount': executionCount, 'messages': messages.map((ConversationMessageDto m) => m.toJson()).toList(), }; } /// Conversation list item (lightweight version for lists) class ConversationListItemDto { final String id; final String title; final String? summary; final DateTime startedAt; final DateTime lastMessageAt; final int messageCount; final bool isActive; final int executionCount; const ConversationListItemDto({ required this.id, required this.title, this.summary, required this.startedAt, required this.lastMessageAt, required this.messageCount, required this.isActive, required this.executionCount, }); factory ConversationListItemDto.fromJson(Map json) { return ConversationListItemDto( id: json['id'] as String, title: json['title'] as String, summary: json['summary'] as String?, startedAt: DateTime.parse(json['startedAt'] as String), lastMessageAt: DateTime.parse(json['lastMessageAt'] as String), messageCount: json['messageCount'] as int, isActive: json['isActive'] as bool, executionCount: json['executionCount'] as int, ); } Map toJson() => { 'id': id, 'title': title, 'summary': summary, 'startedAt': startedAt.toIso8601String(), 'lastMessageAt': lastMessageAt.toIso8601String(), 'messageCount': messageCount, 'isActive': isActive, 'executionCount': executionCount, }; } // ============================================================================= // Extension Methods // ============================================================================= /// Conversation management endpoints extension ConversationEndpoint on CqrsApiClient { /// Create a new conversation /// /// Returns the ID of the newly created conversation. /// /// Example: /// ```dart /// final result = await client.createConversation( /// CreateConversationCommand( /// title: 'My First Conversation', /// summary: 'Optional summary', /// ), /// ); /// /// result.when( /// success: (created) => print('Conversation ID: ${created.id}'), /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future> createConversation( CreateConversationCommand command, ) async { // This is a special command that returns data (conversation ID) // We use executeQuery pattern but with command endpoint try { final Uri url = Uri.parse('${config.baseUrl}/api/command/createConversation'); final String body = jsonEncode(command.toJson()); final http.Response response = await http .post( url, headers: config.defaultHeaders, body: body, ) .timeout(config.timeout); if (response.statusCode >= 200 && response.statusCode < 300) { try { final Object? json = jsonDecode(response.body); final CreateConversationResult result = CreateConversationResult.fromJson(json as Map); return ApiSuccess(result); } catch (e) { return ApiError( ApiErrorInfo( message: 'Failed to parse create conversation response', statusCode: response.statusCode, type: ApiErrorType.serialization, details: e.toString(), ), ); } } else { return ApiError( ApiErrorInfo( message: 'Create conversation failed', statusCode: response.statusCode, type: ApiErrorType.http, ), ); } } on TimeoutException catch (e) { return ApiError( ApiErrorInfo( message: 'Request timeout: ${e.message ?? "Operation took too long"}', type: ApiErrorType.timeout, ), ); } on SocketException catch (e) { return ApiError( ApiErrorInfo( message: 'Network error: ${e.message}', type: ApiErrorType.network, details: e.osError?.message, ), ); } catch (e) { return ApiError( ApiErrorInfo( message: 'Unexpected error: $e', type: ApiErrorType.unknown, ), ); } } /// Get a single conversation by ID with full details /// /// Example: /// ```dart /// final result = await client.getConversation('conversation-uuid'); /// /// result.when( /// success: (conversation) { /// print('Title: ${conversation.title}'); /// print('Messages: ${conversation.messageCount}'); /// for (final message in conversation.messages) { /// print('${message.role}: ${message.content}'); /// } /// }, /// error: (error) => print('Error: ${error.message}'), /// ); /// ``` Future> getConversation(String id) async { return executeQuery( endpoint: 'getConversation', query: GetConversationQuery(id: id), fromJson: (Object? json) => ConversationDto.fromJson(json as Map), ); } }