import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; class ConvaiService { static const String _baseUrl = 'https://api.convai.com/character/getResponse'; String _apiKey = ''; String _characterId = ''; String _sessionId = '-1'; // Remove default values for security - users must set their own static const String defaultApiKey = ''; static const String defaultCharacterId = ''; Future loadSettings() async { final prefs = await SharedPreferences.getInstance(); _apiKey = prefs.getString('api_key') ?? defaultApiKey; _characterId = prefs.getString('character_id') ?? defaultCharacterId; } Future saveSettings(String apiKey, String characterId) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('api_key', apiKey); await prefs.setString('character_id', characterId); _apiKey = apiKey; _characterId = characterId; } String get apiKey => _apiKey; String get characterId => _characterId; bool get isConfigured => _apiKey.isNotEmpty && _characterId.isNotEmpty; void resetSession() { _sessionId = '-1'; } Future sendMessage(String userText) async { if (_apiKey.isEmpty || _characterId.isEmpty) { throw Exception('API key or Character ID not set. Please check settings.'); } try { final request = http.MultipartRequest('POST', Uri.parse(_baseUrl)); // Add headers request.headers['CONVAI-API-KEY'] = _apiKey; // Add form fields request.fields['userText'] = userText; request.fields['charID'] = _characterId; request.fields['sessionID'] = _sessionId; request.fields['voiceResponse'] = 'False'; final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); if (response.statusCode == 200) { final data = json.decode(response.body); _sessionId = data['sessionID'] ?? _sessionId; return data['text'] ?? 'No response received'; } else { throw Exception('HTTP ${response.statusCode}: ${response.reasonPhrase}'); } } catch (e) { throw Exception('Failed to send message: $e'); } } // Get list of saved character IDs Future> getSavedCharacters() async { final prefs = await SharedPreferences.getInstance(); return prefs.getStringList('saved_characters') ?? []; } // Save a new character ID Future saveCharacter(String characterId) async { final prefs = await SharedPreferences.getInstance(); final characters = await getSavedCharacters(); if (!characters.contains(characterId)) { characters.add(characterId); await prefs.setStringList('saved_characters', characters); } } // Remove a character ID Future removeCharacter(String characterId) async { final prefs = await SharedPreferences.getInstance(); final characters = await getSavedCharacters(); characters.remove(characterId); await prefs.setStringList('saved_characters', characters); // Clean up name and initial prompt entries for this character final names = await _getCharacterNamesMap(); if (names.remove(characterId) != null) { await _setCharacterNamesMap(names); } final prompts = await _getCharacterInitialPromptsMap(); if (prompts.remove(characterId) != null) { await _setCharacterInitialPromptsMap(prompts); } } // Rename an existing character ID and migrate associated name and initial prompt Future renameCharacterId(String oldCharacterId, String newCharacterId) async { final oldId = oldCharacterId.trim(); final newId = newCharacterId.trim(); if (newId.isEmpty) { throw Exception('New character ID cannot be empty'); } if (oldId == newId) return; final prefs = await SharedPreferences.getInstance(); final characters = await getSavedCharacters(); if (!characters.contains(oldId)) { throw Exception('Character not found'); } if (characters.contains(newId)) { throw Exception('A character with this ID already exists'); } // Replace in list preserving order final index = characters.indexOf(oldId); characters[index] = newId; await prefs.setStringList('saved_characters', characters); // Migrate name final names = await _getCharacterNamesMap(); if (names.containsKey(oldId)) { names[newId] = names[oldId] ?? ''; names.remove(oldId); await _setCharacterNamesMap(names); } // Migrate initial prompt final prompts = await _getCharacterInitialPromptsMap(); if (prompts.containsKey(oldId)) { prompts[newId] = prompts[oldId] ?? ''; prompts.remove(oldId); await _setCharacterInitialPromptsMap(prompts); } // Update current configured character if it matches if (_characterId == oldId) { await prefs.setString('character_id', newId); _characterId = newId; } } // Character names management Future> _getCharacterNamesMap() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString('character_names_json'); if (raw == null || raw.isEmpty) return {}; try { final decoded = json.decode(raw) as Map; return decoded.map((k, v) => MapEntry(k, v?.toString() ?? '')); } catch (_) { return {}; } } Future _setCharacterNamesMap(Map map) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('character_names_json', json.encode(map)); } Future getCharacterName(String characterId) async { final map = await _getCharacterNamesMap(); return map[characterId] ?? ''; } Future setCharacterName(String characterId, String name) async { final map = await _getCharacterNamesMap(); if (name.isEmpty) { map.remove(characterId); } else { map[characterId] = name; } await _setCharacterNamesMap(map); } Future> getAllCharacterNames() async { return _getCharacterNamesMap(); } // Character initial prompts management Future> _getCharacterInitialPromptsMap() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString('character_initial_prompts_json'); if (raw == null || raw.isEmpty) return {}; try { final decoded = json.decode(raw) as Map; return decoded.map((k, v) => MapEntry(k, v?.toString() ?? '')); } catch (_) { return {}; } } Future _setCharacterInitialPromptsMap(Map map) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString('character_initial_prompts_json', json.encode(map)); } Future getCharacterInitialPrompt(String characterId) async { final map = await _getCharacterInitialPromptsMap(); return map[characterId] ?? ''; } Future setCharacterInitialPrompt(String characterId, String prompt) async { final map = await _getCharacterInitialPromptsMap(); if (prompt.isEmpty) { map.remove(characterId); } else { map[characterId] = prompt; } await _setCharacterInitialPromptsMap(map); } // Supervised mode Future isSupervisedModeEnabled() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool('supervised_mode') ?? false; } Future setSupervisedModeEnabled(bool enabled) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('supervised_mode', enabled); } // Timed experiment settings Future isTimedExperimentEnabled() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool('timed_experiment_enabled') ?? false; } Future setTimedExperimentEnabled(bool enabled) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool('timed_experiment_enabled', enabled); } Future getExperimentDurationMinutes() async { final prefs = await SharedPreferences.getInstance(); return prefs.getInt('experiment_duration_minutes') ?? 5; } Future setExperimentDurationMinutes(int minutes) async { final prefs = await SharedPreferences.getInstance(); await prefs.setInt('experiment_duration_minutes', minutes); } }