analysis_api_service.dart 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import 'dart:convert';
  2. import 'package:http/http.dart' as http;
  3. import '../data/models/analysis_models.dart';
  4. class AnalysisApiService {
  5. AnalysisApiService({http.Client? client, String? baseUrl})
  6. : _client = client ?? http.Client(),
  7. _baseUri = _normalizeBaseUri(baseUrl ?? 'http://127.0.0.1:8000/api');
  8. final http.Client _client;
  9. final Uri _baseUri;
  10. Future<ObservationSummary> createObservation({
  11. required int durationMs,
  12. required int sampleRate,
  13. required int channels,
  14. List<String> tags = const [],
  15. Map<String, dynamic> captureMetadata = const {},
  16. }) async {
  17. final response = await _client.post(
  18. _endpointUri('observations'),
  19. headers: {'Content-Type': 'application/json'},
  20. body: jsonEncode({
  21. 'modality': 'audio',
  22. 'duration_ms': durationMs,
  23. 'sample_rate': sampleRate,
  24. 'channels': channels,
  25. 'tags': tags,
  26. 'capture_metadata': captureMetadata,
  27. }),
  28. );
  29. return ObservationSummary.fromJson(
  30. _decodeJson(response) as Map<String, dynamic>,
  31. );
  32. }
  33. Future<ObservationSummary> uploadObservation({
  34. required String filePath,
  35. required int durationMs,
  36. required int sampleRate,
  37. required int channels,
  38. List<String> tags = const [],
  39. Map<String, dynamic> captureMetadata = const {},
  40. }) async {
  41. final request = http.MultipartRequest(
  42. 'POST',
  43. _endpointUri('observations/upload'),
  44. );
  45. request.fields['duration_ms'] = '$durationMs';
  46. request.fields['sample_rate'] = '$sampleRate';
  47. request.fields['channels'] = '$channels';
  48. request.fields['tags_json'] = jsonEncode(tags);
  49. request.fields['capture_metadata_json'] = jsonEncode(captureMetadata);
  50. request.files.add(
  51. await http.MultipartFile.fromPath('audio_file', filePath),
  52. );
  53. final streamed = await request.send();
  54. final response = await http.Response.fromStream(streamed);
  55. return ObservationSummary.fromJson(
  56. _decodeJson(response) as Map<String, dynamic>,
  57. );
  58. }
  59. Future<AnalysisSessionSummary> createSession({
  60. required String observationId,
  61. String mode = 'initial_analysis',
  62. }) async {
  63. final response = await _client.post(
  64. _endpointUri('analysis/sessions'),
  65. headers: {'Content-Type': 'application/json'},
  66. body: jsonEncode({'observation_id': observationId, 'mode': mode}),
  67. );
  68. return AnalysisSessionSummary.fromJson(
  69. _decodeJson(response) as Map<String, dynamic>,
  70. );
  71. }
  72. Future<List<AnalysisSessionSummary>> listSessions() async {
  73. final response = await _client.get(_endpointUri('analysis/sessions'));
  74. final json = _decodeJson(response) as Map<String, dynamic>;
  75. final items = (json['items'] as List<dynamic>? ?? const []);
  76. return items
  77. .map(
  78. (item) =>
  79. AnalysisSessionSummary.fromJson(item as Map<String, dynamic>),
  80. )
  81. .toList();
  82. }
  83. Future<List<ObservationSummary>> listObservations() async {
  84. final response = await _client.get(_endpointUri('observations'));
  85. final json = _decodeJson(response) as Map<String, dynamic>;
  86. final items = (json['items'] as List<dynamic>? ?? const []);
  87. return items
  88. .map(
  89. (item) => ObservationSummary.fromJson(item as Map<String, dynamic>),
  90. )
  91. .toList();
  92. }
  93. Future<ObservationSummary> getObservation(String observationId) async {
  94. final response = await _client.get(_endpointUri('observations/$observationId'));
  95. return ObservationSummary.fromJson(
  96. _decodeJson(response) as Map<String, dynamic>,
  97. );
  98. }
  99. Future<AnalysisSessionSnapshot> getSession(String sessionId) async {
  100. final response = await _client.get(
  101. _endpointUri('analysis/sessions/$sessionId'),
  102. );
  103. return AnalysisSessionSnapshot.fromJson(
  104. _decodeJson(response) as Map<String, dynamic>,
  105. );
  106. }
  107. Object _decodeJson(http.Response response) {
  108. if (response.statusCode < 200 || response.statusCode >= 300) {
  109. throw AnalysisApiException(
  110. 'Backend request failed with status ${response.statusCode}: ${response.body}',
  111. );
  112. }
  113. return jsonDecode(response.body);
  114. }
  115. Uri _endpointUri(String path) => _baseUri.resolve(path);
  116. static Uri _normalizeBaseUri(String rawBaseUrl) {
  117. final normalized = rawBaseUrl.endsWith('/') ? rawBaseUrl : '$rawBaseUrl/';
  118. return Uri.parse(normalized);
  119. }
  120. }
  121. class AnalysisApiException implements Exception {
  122. AnalysisApiException(this.message);
  123. final String message;
  124. @override
  125. String toString() => message;
  126. }