import 'dart:convert'; import 'package:http/http.dart' as http; import '../data/models/analysis_models.dart'; class AnalysisApiService { AnalysisApiService({http.Client? client, String? baseUrl}) : _client = client ?? http.Client(), _baseUri = _normalizeBaseUri(baseUrl ?? 'http://127.0.0.1:8000/api'); final http.Client _client; final Uri _baseUri; Future createObservation({ required int durationMs, required int sampleRate, required int channels, List tags = const [], Map captureMetadata = const {}, }) async { final response = await _client.post( _endpointUri('observations'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'modality': 'audio', 'duration_ms': durationMs, 'sample_rate': sampleRate, 'channels': channels, 'tags': tags, 'capture_metadata': captureMetadata, }), ); return ObservationSummary.fromJson( _decodeJson(response) as Map, ); } Future uploadObservation({ required String filePath, required int durationMs, required int sampleRate, required int channels, List tags = const [], Map captureMetadata = const {}, }) async { final request = http.MultipartRequest( 'POST', _endpointUri('observations/upload'), ); request.fields['duration_ms'] = '$durationMs'; request.fields['sample_rate'] = '$sampleRate'; request.fields['channels'] = '$channels'; request.fields['tags_json'] = jsonEncode(tags); request.fields['capture_metadata_json'] = jsonEncode(captureMetadata); request.files.add( await http.MultipartFile.fromPath('audio_file', filePath), ); final streamed = await request.send(); final response = await http.Response.fromStream(streamed); return ObservationSummary.fromJson( _decodeJson(response) as Map, ); } Future createSession({ required String observationId, String mode = 'initial_analysis', }) async { final response = await _client.post( _endpointUri('analysis/sessions'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'observation_id': observationId, 'mode': mode}), ); return AnalysisSessionSummary.fromJson( _decodeJson(response) as Map, ); } Future> listSessions() async { final response = await _client.get(_endpointUri('analysis/sessions')); final json = _decodeJson(response) as Map; final items = (json['items'] as List? ?? const []); return items .map( (item) => AnalysisSessionSummary.fromJson(item as Map), ) .toList(); } Future> listObservations() async { final response = await _client.get(_endpointUri('observations')); final json = _decodeJson(response) as Map; final items = (json['items'] as List? ?? const []); return items .map( (item) => ObservationSummary.fromJson(item as Map), ) .toList(); } Future getObservation(String observationId) async { final response = await _client.get(_endpointUri('observations/$observationId')); return ObservationSummary.fromJson( _decodeJson(response) as Map, ); } Future getSession(String sessionId) async { final response = await _client.get( _endpointUri('analysis/sessions/$sessionId'), ); return AnalysisSessionSnapshot.fromJson( _decodeJson(response) as Map, ); } Object _decodeJson(http.Response response) { if (response.statusCode < 200 || response.statusCode >= 300) { throw AnalysisApiException( 'Backend request failed with status ${response.statusCode}: ${response.body}', ); } return jsonDecode(response.body); } Uri _endpointUri(String path) => _baseUri.resolve(path); static Uri _normalizeBaseUri(String rawBaseUrl) { final normalized = rawBaseUrl.endsWith('/') ? rawBaseUrl : '$rawBaseUrl/'; return Uri.parse(normalized); } } class AnalysisApiException implements Exception { AnalysisApiException(this.message); final String message; @override String toString() => message; }