import 'dart:convert'; import 'package:http/http.dart' as http; import '../config/app_config.dart'; import '../models/persona.dart'; import '../models/pictogram.dart'; import '../models/prediction.dart'; class ApiException implements Exception { final int statusCode; final String message; ApiException(this.statusCode, this.message); @override String toString() => 'ApiException: $statusCode - $message'; } class ApiTimeoutException implements Exception { final String message; ApiTimeoutException(this.message); @override String toString() => 'ApiTimeoutException: $message'; } class HealthStatus { final bool isOk; final bool modelLoaded; HealthStatus(this.isOk, this.modelLoaded); } class AnkahiApi { final String baseUrl = AppConfig.apiBaseUrl; Future predict( List sequence, String personaId, { String? context, String? audioHint }) async { final response = await http.post( Uri.parse('$baseUrl/predict'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'pictogram_sequence': sequence, 'persona_id': personaId, 'context': context ?? 'daily', 'audio_transcript': audioHint, }), ).timeout(AppConfig.apiTimeout, onTimeout: () { throw ApiTimeoutException('Prediction timed out'); }); if (response.statusCode == 200) { return PredictionResult.fromJson(jsonDecode(response.body)); } else { throw ApiException(response.statusCode, response.body); } } Future speak(String text, String personaId, String language) async { final response = await http.post( Uri.parse('$baseUrl/speak'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'text': text, 'persona_id': personaId, 'language': language, }), ).timeout(AppConfig.apiTimeout, onTimeout: () { throw ApiTimeoutException('Speak request timed out'); }); if (response.statusCode == 200) { final data = jsonDecode(response.body); return '$baseUrl${data['audio_url']}'; } else { throw ApiException(response.statusCode, response.body); } } Future logUtterance( List seq, String confirmed, String personaId, { bool wasFirst = true }) async { try { await http.post( Uri.parse('$baseUrl/utterances/log'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'pictogram_sequence': seq, 'confirmed_sentence': confirmed, 'persona_id': personaId, 'was_first_alternative': wasFirst, }), ).timeout(const Duration(seconds: 10)); } catch (e) { // Background task, ignore errors print('Failed to log utterance: $e'); } } Future> getPersonas() async { final response = await http.get(Uri.parse('$baseUrl/personas')) .timeout(AppConfig.apiTimeout); if (response.statusCode == 200) { final List data = jsonDecode(response.body); return data.map((json) => PersonaModel.fromJson(json)).toList(); } else { throw ApiException(response.statusCode, response.body); } } Future switchPersona(String personaId) async { final response = await http.post(Uri.parse('$baseUrl/personas/switch/$personaId')) .timeout(AppConfig.apiTimeout); if (response.statusCode == 200) { return true; } else { throw ApiException(response.statusCode, response.body); } } Future> searchSymbols( String query, { String language = 'en', int limit = 20 }) async { final response = await http.get( Uri.parse('$baseUrl/symbols/search?query=$query&language=$language&limit=$limit') ).timeout(AppConfig.apiTimeout); if (response.statusCode == 200) { final List data = jsonDecode(response.body); return data.map((json) => PictogramModel.fromJson(json)).toList(); } else { throw ApiException(response.statusCode, response.body); } } Future getHealth() async { try { final response = await http.get(Uri.parse('$baseUrl/health')) .timeout(const Duration(seconds: 5)); if (response.statusCode == 200) { final data = jsonDecode(response.body); return HealthStatus(true, data['model_loaded']); } } catch (e) { print('Health check failed: $e'); } return HealthStatus(false, false); } }