From e184e9a76f4ed159273b52b31a9ef0f4365ff945 Mon Sep 17 00:00:00 2001 From: ggurdin Date: Wed, 3 Dec 2025 15:39:29 -0500 Subject: [PATCH] move text to speech controller out of pangea controller --- lib/pages/chat/events/message_content.dart | 2 +- lib/pangea/choreographer/choreographer.dart | 2 +- .../common/controllers/pangea_controller.dart | 5 +- lib/pangea/common/widgets/choice_array.dart | 2 +- .../event_wrappers/pangea_message_event.dart | 16 +- .../extensions/pangea_event_extension.dart | 2 +- .../pages/settings_learning.dart | 2 +- .../phonetic_transcription_widget.dart | 2 +- .../text_to_speech/text_to_speech_repo.dart | 105 ++++++++ .../text_to_speech_request_model.dart | 38 +++ .../text_to_speech_response_model.dart | 117 +++++++++ .../tts_controller.dart | 52 ++-- .../text_to_speech_controller.dart | 243 ------------------ .../practice_match_item.dart | 2 +- .../toolbar/widgets/message_audio_card.dart | 2 +- .../widgets/message_selection_overlay.dart | 2 +- .../practice_activity/word_audio_button.dart | 2 +- .../toolbar/widgets/select_mode_buttons.dart | 2 +- 18 files changed, 308 insertions(+), 290 deletions(-) create mode 100644 lib/pangea/text_to_speech/text_to_speech_repo.dart create mode 100644 lib/pangea/text_to_speech/text_to_speech_request_model.dart create mode 100644 lib/pangea/text_to_speech/text_to_speech_response_model.dart rename lib/pangea/{toolbar/controllers => text_to_speech}/tts_controller.dart (90%) delete mode 100644 lib/pangea/toolbar/controllers/text_to_speech_controller.dart diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 9f31ec73d..7aa5794e4 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/video_player.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/enums/reading_assistance_mode_enum.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/utils/event_checkbox_extension.dart'; diff --git a/lib/pangea/choreographer/choreographer.dart b/lib/pangea/choreographer/choreographer.dart index 8c936a3d7..c0a63bca2 100644 --- a/lib/pangea/choreographer/choreographer.dart +++ b/lib/pangea/choreographer/choreographer.dart @@ -21,7 +21,7 @@ import 'package:fluffychat/pangea/events/repo/tokens_repo.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; import 'package:fluffychat/pangea/spaces/models/space_model.dart'; import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import '../../widgets/matrix.dart'; import 'choreographer_error_controller.dart'; diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index 2595b1819..9208816af 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -22,8 +22,7 @@ import 'package:fluffychat/pangea/learning_settings/utils/locale_provider.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/pangea/spaces/controllers/space_code_controller.dart'; import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/user/controllers/permissions_controller.dart'; import 'package:fluffychat/pangea/user/controllers/user_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -38,7 +37,6 @@ class PangeaController { late GetAnalyticsController getAnalytics; late PutAnalyticsController putAnalytics; late SubscriptionController subscriptionController; - late TextToSpeechController textToSpeech; ///store Services final pLanguageStore = PLanguageStore(); @@ -82,7 +80,6 @@ class PangeaController { getAnalytics = GetAnalyticsController(this); putAnalytics = PutAnalyticsController(this); subscriptionController = SubscriptionController(this); - textToSpeech = TextToSpeechController(this); PAuthGaurd.pController = this; } diff --git a/lib/pangea/common/widgets/choice_array.dart b/lib/pangea/common/widgets/choice_array.dart index 3a953f0e6..d0c109259 100644 --- a/lib/pangea/common/widgets/choice_array.dart +++ b/lib/pangea/common/widgets/choice_array.dart @@ -5,7 +5,7 @@ import 'package:collection/collection.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/common/widgets/choice_animation.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; import '../../bot/utils/bot_style.dart'; import '../../choreographer/it/it_shimmer.dart'; diff --git a/lib/pangea/events/event_wrappers/pangea_message_event.dart b/lib/pangea/events/event_wrappers/pangea_message_event.dart index 230974851..621986924 100644 --- a/lib/pangea/events/event_wrappers/pangea_message_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_message_event.dart @@ -24,7 +24,9 @@ import 'package:fluffychat/pangea/speech_to_text/audio_encoding_enum.dart'; import 'package:fluffychat/pangea/speech_to_text/speech_to_text_repo.dart'; import 'package:fluffychat/pangea/speech_to_text/speech_to_text_request_model.dart'; import 'package:fluffychat/pangea/speech_to_text/speech_to_text_response_model.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; import 'package:fluffychat/pangea/translation/full_text_translation_repo.dart'; import 'package:fluffychat/pangea/translation/full_text_translation_request_model.dart'; @@ -360,7 +362,7 @@ class PangeaMessageEvent { final rep = representationByLanguage(langCode); final tokensResp = await rep?.requestTokens(); - final request = TextToSpeechRequest( + final request = TextToSpeechRequestModel( text: rep?.content.text ?? body, tokens: tokensResp?.result?.map((t) => t.text).toList() ?? [], langCode: langCode, @@ -368,10 +370,18 @@ class PangeaMessageEvent { userL2: _l2Code ?? LanguageKeys.unknownLanguage, ); - final response = await MatrixState.pangeaController.textToSpeech.get( + final result = await TextToSpeechRepo.get( + MatrixState.pangeaController.userController.accessToken, request, ); + if (result.error != null) { + throw Exception( + "Error getting text to speech: ${result.error}", + ); + } + + final response = result.result!; final audioBytes = base64.decode(response.audioContent); final fileName = "audio_for_${_event.eventId}_$langCode.${response.fileExtension}"; diff --git a/lib/pangea/events/extensions/pangea_event_extension.dart b/lib/pangea/events/extensions/pangea_event_extension.dart index 77014bfcf..8d18c5ec5 100644 --- a/lib/pangea/events/extensions/pangea_event_extension.dart +++ b/lib/pangea/events/extensions/pangea_event_extension.dart @@ -11,7 +11,7 @@ import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_activity_model.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; extension PangeaEvent on Event { diff --git a/lib/pangea/learning_settings/pages/settings_learning.dart b/lib/pangea/learning_settings/pages/settings_learning.dart index c57c66229..e894bca95 100644 --- a/lib/pangea/learning_settings/pages/settings_learning.dart +++ b/lib/pangea/learning_settings/pages/settings_learning.dart @@ -14,7 +14,7 @@ import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/learning_settings/pages/settings_learning_view.dart'; import 'package:fluffychat/pangea/learning_settings/utils/p_language_store.dart'; import 'package:fluffychat/pangea/spaces/models/space_model.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/user/models/user_model.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; diff --git a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart index a610cc28b..df866e653 100644 --- a/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart +++ b/lib/pangea/phonetic_transcription/phonetic_transcription_widget.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_repo.dart'; import 'package:fluffychat/pangea/phonetic_transcription/phonetic_transcription_request.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/matrix.dart'; diff --git a/lib/pangea/text_to_speech/text_to_speech_repo.dart b/lib/pangea/text_to_speech/text_to_speech_repo.dart new file mode 100644 index 000000000..3b68486e6 --- /dev/null +++ b/lib/pangea/text_to_speech/text_to_speech_repo.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; + +import 'package:async/async.dart'; +import 'package:http/http.dart'; + +import 'package:fluffychat/pangea/common/config/environment.dart'; +import 'package:fluffychat/pangea/common/network/requests.dart'; +import 'package:fluffychat/pangea/common/network/urls.dart'; +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; + +class _TextToSpeechCacheItem { + final Future data; + final DateTime timestamp; + + const _TextToSpeechCacheItem({ + required this.data, + required this.timestamp, + }); +} + +class TextToSpeechRepo { + static final Map _cache = {}; + static const Duration _cacheDuration = Duration(minutes: 10); + + static Future> get( + String accessToken, + TextToSpeechRequestModel request, + ) { + final cached = _getCached(request); + if (cached != null) { + return _getResult(request, cached); + } + + final future = _fetch(accessToken, request); + _setCached(request, future); + return _getResult(request, future); + } + + static Future _fetch( + String accessToken, + TextToSpeechRequestModel request, + ) async { + final Requests req = Requests( + choreoApiKey: Environment.choreoApiKey, + accessToken: accessToken, + ); + + final Response res = await req.post( + url: PApiUrls.textToSpeech, + body: request.toJson(), + ); + + if (res.statusCode != 200) { + throw Exception( + 'Failed to convert text to speech: ${res.statusCode} ${res.reasonPhrase}', + ); + } + + return TextToSpeechResponseModel.fromJson( + jsonDecode(utf8.decode(res.bodyBytes)), + ); + } + + static Future> _getResult( + TextToSpeechRequestModel request, + Future future, + ) async { + try { + final res = await future; + return Result.value(res); + } catch (e, s) { + _cache.remove(request.hashCode.toString()); + ErrorHandler.logError( + e: e, + s: s, + data: request.toJson(), + ); + return Result.error(e); + } + } + + static Future? _getCached( + TextToSpeechRequestModel request, + ) { + final cacheKeys = [..._cache.keys]; + for (final key in cacheKeys) { + if (DateTime.now().difference(_cache[key]!.timestamp) >= _cacheDuration) { + _cache.remove(key); + } + } + + return _cache[request.hashCode.toString()]?.data; + } + + static void _setCached( + TextToSpeechRequestModel request, + Future response, + ) => + _cache[request.hashCode.toString()] = _TextToSpeechCacheItem( + data: response, + timestamp: DateTime.now(), + ); +} diff --git a/lib/pangea/text_to_speech/text_to_speech_request_model.dart b/lib/pangea/text_to_speech/text_to_speech_request_model.dart new file mode 100644 index 000000000..9c18104f7 --- /dev/null +++ b/lib/pangea/text_to_speech/text_to_speech_request_model.dart @@ -0,0 +1,38 @@ +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; + +class TextToSpeechRequestModel { + String text; + String langCode; + String userL1; + String userL2; + List tokens; + + TextToSpeechRequestModel({ + required this.text, + required this.langCode, + required this.userL1, + required this.userL2, + required this.tokens, + }); + + Map toJson() => { + ModelKey.text: text, + ModelKey.langCode: langCode, + ModelKey.userL1: userL1, + ModelKey.userL2: userL2, + ModelKey.tokens: tokens.map((token) => token.toJson()).toList(), + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is TextToSpeechRequestModel && + other.text == text && + other.langCode == langCode; + } + + @override + int get hashCode => text.hashCode ^ langCode.hashCode; +} diff --git a/lib/pangea/text_to_speech/text_to_speech_response_model.dart b/lib/pangea/text_to_speech/text_to_speech_response_model.dart new file mode 100644 index 000000000..24cc71e1a --- /dev/null +++ b/lib/pangea/text_to_speech/text_to_speech_response_model.dart @@ -0,0 +1,117 @@ +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; +import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; + +class TextToSpeechResponseModel { + String audioContent; + String mimeType; + int durationMillis; + List waveform; + String fileExtension; + List ttsTokens; + + TextToSpeechResponseModel({ + required this.audioContent, + required this.mimeType, + required this.durationMillis, + required this.waveform, + required this.fileExtension, + required this.ttsTokens, + }); + + factory TextToSpeechResponseModel.fromJson( + Map json, + ) => + TextToSpeechResponseModel( + audioContent: json["audio_content"], + mimeType: json["mime_type"], + durationMillis: json["duration_millis"], + waveform: List.from(json["wave_form"]), + fileExtension: json["file_extension"], + ttsTokens: List.from( + json["tts_tokens"].map((x) => TTSToken.fromJson(x)), + ), + ); + + Map toJson() => { + "audio_content": audioContent, + "mime_type": mimeType, + "duration_millis": durationMillis, + "wave_form": List.from(waveform.map((x) => x)), + "file_extension": fileExtension, + "tts_tokens": List.from(ttsTokens.map((x) => x.toJson())), + }; + + PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) { + return PangeaAudioEventData( + text: text, + langCode: langCode, + tokens: ttsTokens, + ); + } +} + +class TTSToken { + final int startMS; + final int endMS; + final PangeaTokenText text; + + TTSToken({ + required this.startMS, + required this.endMS, + required this.text, + }); + + factory TTSToken.fromJson(Map json) => TTSToken( + startMS: json["start_ms"], + endMS: json["end_ms"], + text: PangeaTokenText.fromJson(json["text"]), + ); + + Map toJson() => { + "start_ms": startMS, + "end_ms": endMS, + "text": text.toJson(), + }; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is TTSToken && + other.startMS == startMS && + other.endMS == endMS && + other.text == text; + } + + @override + int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode; +} + +class PangeaAudioEventData { + final String text; + final String langCode; + final List tokens; + + PangeaAudioEventData({ + required this.text, + required this.langCode, + required this.tokens, + }); + + factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData( + text: json[ModelKey.text] as String, + langCode: json[ModelKey.langCode] as String, + tokens: List.from( + (json[ModelKey.tokens] as Iterable) + .map((x) => TTSToken.fromJson(x)) + .toList(), + ), + ); + + Map toJson() => { + ModelKey.text: text, + ModelKey.langCode: langCode, + ModelKey.tokens: + List>.from(tokens.map((x) => x.toJson())), + }; +} diff --git a/lib/pangea/toolbar/controllers/tts_controller.dart b/lib/pangea/text_to_speech/tts_controller.dart similarity index 90% rename from lib/pangea/toolbar/controllers/tts_controller.dart rename to lib/pangea/text_to_speech/tts_controller.dart index a7f90f52a..6ca6745f5 100644 --- a/lib/pangea/toolbar/controllers/tts_controller.dart +++ b/lib/pangea/text_to_speech/tts_controller.dart @@ -20,7 +20,10 @@ import 'package:fluffychat/pangea/common/widgets/card_header.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_repo.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_request_model.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart' @@ -263,35 +266,26 @@ class TtsController { String langCode, List tokens, ) async { - TextToSpeechResponse? ttsRes; - try { - loadingChoreoStream.add(true); - ttsRes = await MatrixState.pangeaController.textToSpeech.get( - TextToSpeechRequest( - text: text, - langCode: langCode, - tokens: tokens, - userL1: - MatrixState.pangeaController.languageController.activeL1Code() ?? - LanguageKeys.unknownLanguage, - userL2: - MatrixState.pangeaController.languageController.activeL2Code() ?? - LanguageKeys.unknownLanguage, - ), - ); - } catch (e, s) { - error_handler.ErrorHandler.logError( - e: e, - s: s, - data: { - 'text': text, - }, - ); - } finally { - loadingChoreoStream.add(false); - } + TextToSpeechResponseModel? ttsRes; - if (ttsRes == null) return; + loadingChoreoStream.add(true); + final result = await TextToSpeechRepo.get( + MatrixState.pangeaController.userController.accessToken, + TextToSpeechRequestModel( + text: text, + langCode: langCode, + tokens: tokens, + userL1: + MatrixState.pangeaController.languageController.activeL1Code() ?? + LanguageKeys.unknownLanguage, + userL2: + MatrixState.pangeaController.languageController.activeL2Code() ?? + LanguageKeys.unknownLanguage, + ), + ); + loadingChoreoStream.add(false); + if (result.isError) return; + ttsRes = result.result!; try { Logs().i('Speaking from choreo: $text, langCode: $langCode'); diff --git a/lib/pangea/toolbar/controllers/text_to_speech_controller.dart b/lib/pangea/toolbar/controllers/text_to_speech_controller.dart deleted file mode 100644 index 54a9768ca..000000000 --- a/lib/pangea/toolbar/controllers/text_to_speech_controller.dart +++ /dev/null @@ -1,243 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/config/environment.dart'; -import 'package:fluffychat/pangea/common/constants/model_keys.dart'; -import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; -import 'package:fluffychat/pangea/common/network/requests.dart'; -import 'package:fluffychat/pangea/common/network/urls.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; - -class PangeaAudioEventData { - final String text; - final String langCode; - final List tokens; - - PangeaAudioEventData({ - required this.text, - required this.langCode, - required this.tokens, - }); - - factory PangeaAudioEventData.fromJson(dynamic json) => PangeaAudioEventData( - text: json[ModelKey.text] as String, - langCode: json[ModelKey.langCode] as String, - tokens: List.from( - (json[ModelKey.tokens] as Iterable) - .map((x) => TTSToken.fromJson(x)) - .toList(), - ), - ); - - Map toJson() => { - ModelKey.text: text, - ModelKey.langCode: langCode, - ModelKey.tokens: - List>.from(tokens.map((x) => x.toJson())), - }; -} - -class TTSToken { - final int startMS; - final int endMS; - final PangeaTokenText text; - - TTSToken({ - required this.startMS, - required this.endMS, - required this.text, - }); - - factory TTSToken.fromJson(Map json) => TTSToken( - startMS: json["start_ms"], - endMS: json["end_ms"], - text: PangeaTokenText.fromJson(json["text"]), - ); - - Map toJson() => { - "start_ms": startMS, - "end_ms": endMS, - "text": text.toJson(), - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is TTSToken && - other.startMS == startMS && - other.endMS == endMS && - other.text == text; - } - - @override - int get hashCode => startMS.hashCode ^ endMS.hashCode ^ text.hashCode; -} - -class TextToSpeechRequest { - String text; - String langCode; - String userL1; - String userL2; - List tokens; - - TextToSpeechRequest({ - required this.text, - required this.langCode, - required this.userL1, - required this.userL2, - required this.tokens, - }); - - Map toJson() => { - ModelKey.text: text, - ModelKey.langCode: langCode, - ModelKey.userL1: userL1, - ModelKey.userL2: userL2, - ModelKey.tokens: tokens.map((token) => token.toJson()).toList(), - }; - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is TextToSpeechRequest && - other.text == text && - other.langCode == langCode; - } - - @override - int get hashCode => text.hashCode ^ langCode.hashCode; -} - -class TextToSpeechResponse { - String audioContent; - String mimeType; - int durationMillis; - List waveform; - String fileExtension; - List ttsTokens; - - TextToSpeechResponse({ - required this.audioContent, - required this.mimeType, - required this.durationMillis, - required this.waveform, - required this.fileExtension, - required this.ttsTokens, - }); - - factory TextToSpeechResponse.fromJson( - Map json, - ) => - TextToSpeechResponse( - audioContent: json["audio_content"], - mimeType: json["mime_type"], - durationMillis: json["duration_millis"], - waveform: List.from(json["wave_form"]), - fileExtension: json["file_extension"], - ttsTokens: List.from( - json["tts_tokens"].map((x) => TTSToken.fromJson(x)), - ), - ); - - Map toJson() => { - "audio_content": audioContent, - "mime_type": mimeType, - "duration_millis": durationMillis, - "wave_form": List.from(waveform.map((x) => x)), - "file_extension": fileExtension, - "tts_tokens": List.from(ttsTokens.map((x) => x.toJson())), - }; - - PangeaAudioEventData toPangeaAudioEventData(String text, String langCode) { - return PangeaAudioEventData( - text: text, - langCode: langCode, - tokens: ttsTokens, - ); - } -} - -class _TextToSpeechCacheItem { - Future data; - - _TextToSpeechCacheItem({ - required this.data, - }); -} - -class TextToSpeechController { - static final Map _cache = {}; - late final PangeaController _pangeaController; - - Timer? _cacheClearTimer; - - TextToSpeechController(PangeaController pangeaController) { - _pangeaController = pangeaController; - _initializeCacheClearing(); - } - - void _initializeCacheClearing() { - const duration = Duration(minutes: 15); // Adjust the duration as needed - _cacheClearTimer = Timer.periodic(duration, (Timer t) => _clearCache()); - } - - void _clearCache() { - _cache.clear(); - } - - void dispose() { - _cacheClearTimer?.cancel(); - } - - Future get( - TextToSpeechRequest params, - ) async { - if (_cache.containsKey(params)) { - return _cache[params]!.data; - } else { - final Future response = _fetchResponse( - _pangeaController.userController.accessToken, - params, - ); - _cache[params] = _TextToSpeechCacheItem(data: response); - return response; - } - } - - static Future _fetchResponse( - String accessToken, - TextToSpeechRequest params, - ) async { - final Requests request = Requests( - choreoApiKey: Environment.choreoApiKey, - accessToken: accessToken, - ); - - final Response res = await request.post( - url: PApiUrls.textToSpeech, - body: params.toJson(), - ); - - final Map json = jsonDecode(res.body); - - return TextToSpeechResponse.fromJson(json); - } - - static bool isOggFile(Uint8List bytes) { - // Check if the file has enough bytes for the header - if (bytes.length < 4) { - return false; - } - - // Check the magic number for OGG file - return bytes[0] == 0x4F && - bytes[1] == 0x67 && - bytes[2] == 0x67 && - bytes[3] == 0x53; - } -} diff --git a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart index 5ff61829e..87d0e8adc 100644 --- a/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart +++ b/lib/pangea/toolbar/reading_assistance_input_row/practice_match_item.dart @@ -7,7 +7,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/practice_activities/practice_choice.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; diff --git a/lib/pangea/toolbar/widgets/message_audio_card.dart b/lib/pangea/toolbar/widgets/message_audio_card.dart index 626c89e80..82d2624c8 100644 --- a/lib/pangea/toolbar/widgets/message_audio_card.dart +++ b/lib/pangea/toolbar/widgets/message_audio_card.dart @@ -10,7 +10,7 @@ import 'package:fluffychat/pages/chat/events/audio_player.dart'; import 'package:fluffychat/pangea/analytics_misc/text_loading_shimmer.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; class MessageAudioCard extends StatefulWidget { final PangeaMessageEvent messageEvent; diff --git a/lib/pangea/toolbar/widgets/message_selection_overlay.dart b/lib/pangea/toolbar/widgets/message_selection_overlay.dart index c631e858c..148e1814c 100644 --- a/lib/pangea/toolbar/widgets/message_selection_overlay.dart +++ b/lib/pangea/toolbar/widgets/message_selection_overlay.dart @@ -20,7 +20,7 @@ import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_ev import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_text_model.dart'; import 'package:fluffychat/pangea/message_token_text/tokens_util.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/text_to_speech_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/text_to_speech_response_model.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_positioner.dart'; import 'package:fluffychat/pangea/toolbar/widgets/practice_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/select_mode_buttons.dart'; diff --git a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart index cac792e8c..385290e38 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:fluffychat/l10n/l10n.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/widgets/matrix.dart'; class WordAudioButton extends StatefulWidget { diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 5610ecb7b..c824d7236 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -17,7 +17,7 @@ import 'package:fluffychat/pangea/common/widgets/pressable_button.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart'; import 'package:fluffychat/pangea/events/utils/report_message.dart'; -import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart'; +import 'package:fluffychat/pangea/text_to_speech/tts_controller.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_audio_card.dart'; import 'package:fluffychat/pangea/toolbar/widgets/message_selection_overlay.dart'; import 'package:fluffychat/pangea/toolbar/widgets/select_mode_controller.dart';