import 'dart:typed_data'; import 'package:dio/dio.dart'; import 'package:social_app/core/chat/chat_api.dart'; import 'package:social_app/data/network/i_api_client.dart'; class ChatApiImpl implements ChatApi { ChatApiImpl(this._apiClient); final IApiClient _apiClient; @override Future> createRun(Map runInput) async { final response = await _apiClient.post>( '/api/v1/agent/runs', data: runInput, ); final payload = response.data; if (payload is! Map) { throw StateError('Invalid /agent/runs response'); } return payload; } @override Future> streamRunEvents( String threadId, { required String runId, String? lastEventId, }) { final headers = {'Accept': 'text/event-stream'}; if (lastEventId != null && lastEventId.isNotEmpty) { headers['Last-Event-ID'] = lastEventId; } final encodedRunId = Uri.encodeQueryComponent(runId); final path = '/api/v1/agent/runs/$threadId/events?runId=$encodedRunId'; return _apiClient.getSseLines(path, headers: headers); } @override Future> fetchHistory({ String? threadId, DateTime? beforeDate, }) async { final path = _buildHistoryPath(threadId: threadId, beforeDate: beforeDate); final response = await _apiClient.get>(path); final payload = response.data; if (payload is! Map) { throw StateError('Invalid /agent/history response'); } return payload; } @override Future> uploadAttachment({ required String threadId, required String filename, required String mimeType, required Uint8List bytes, }) async { final formData = FormData.fromMap({ 'threadId': threadId, 'file': MultipartFile.fromBytes( bytes, filename: filename, contentType: DioMediaType.parse(mimeType), ), }); final response = await _apiClient.post>( '/api/v1/agent/attachments', data: formData, ); final payload = response.data; if (payload is! Map) { throw StateError('Invalid /agent/attachments response'); } return payload; } @override Future fetchAttachmentPreview(String previewPath) async { final response = await _apiClient.get>( previewPath, options: Options(responseType: ResponseType.bytes), ); final payload = response.data; if (payload is! List) { throw StateError('Invalid attachment payload'); } return Uint8List.fromList(payload); } @override Future transcribeAudio(String filePath) async { final formData = FormData.fromMap({ 'audio': await MultipartFile.fromFile( filePath, filename: 'recording.wav', contentType: DioMediaType('audio', 'wav'), ), }); final response = await _apiClient.post>( '/api/v1/agent/transcribe', data: formData, ); final payload = response.data; if (payload is! Map) { throw StateError('Invalid /agent/transcribe response'); } final transcript = payload['transcript']; if (transcript is! String) { throw StateError('Missing transcript in /agent/transcribe response'); } return transcript; } @override Future cancelRun({ required String threadId, required String runId, }) async { final encodedRunId = Uri.encodeQueryComponent(runId); await _apiClient.post>( '/api/v1/agent/runs/$threadId/cancel?runId=$encodedRunId', ); } static String _buildHistoryPath({String? threadId, DateTime? beforeDate}) { final query = []; if (threadId != null && threadId.isNotEmpty) { query.add('threadId=${Uri.encodeQueryComponent(threadId)}'); } if (beforeDate != null) { final day = DateTime(beforeDate.year, beforeDate.month, beforeDate.day); query.add('before=${day.toIso8601String().substring(0, 10)}'); } if (query.isEmpty) { return '/api/v1/agent/history'; } return '/api/v1/agent/history?${query.join('&')}'; } }