From 75f70c7fdbe97267f72e7f5d7618f621c2b0cd22 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Tue, 20 May 2025 12:50:29 -0400 Subject: [PATCH] 2843 timeout on igc (#2846) * chore: add timeout to tokenization call after message send * chore: add timeout to IGC * chore: add timeout to IT --- lib/pages/chat/chat_view.dart | 7 +- .../widgets/chat_floating_action_button.dart | 3 +- lib/pangea/chat/widgets/chat_input_bar.dart | 22 ++++- .../controllers/choreographer.dart | 95 +++++++++---------- .../controllers/igc_controller.dart | 4 +- .../controllers/it_controller.dart | 5 +- lib/pangea/choreographer/widgets/it_bar.dart | 66 ++++++++----- .../choreographer/widgets/it_bar_buttons.dart | 27 ------ .../controllers/message_data_controller.dart | 9 +- .../pangea_representation_event.dart | 4 +- lib/pangea/events/repo/token_api_models.dart | 5 +- 11 files changed, 129 insertions(+), 118 deletions(-) delete mode 100644 lib/pangea/choreographer/widgets/it_bar_buttons.dart diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 127ae2c40..1a366e6da 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -400,7 +400,12 @@ class ChatView extends StatelessWidget { // #Pangea // Keep messages above minimum input bar height if (!controller.room.isAbandonedDMRoom) - SizedBox(height: controller.inputBarHeight), + AnimatedSize( + duration: const Duration(milliseconds: 200), + child: SizedBox( + height: controller.inputBarHeight, + ), + ), // Pangea# ], ), diff --git a/lib/pangea/chat/widgets/chat_floating_action_button.dart b/lib/pangea/chat/widgets/chat_floating_action_button.dart index a024d5b97..cec4648f0 100644 --- a/lib/pangea/chat/widgets/chat_floating_action_button.dart +++ b/lib/pangea/chat/widgets/chat_floating_action_button.dart @@ -74,7 +74,8 @@ class ChatFloatingActionButtonState extends State { child: const Icon(Icons.arrow_downward_outlined), ); } - if (widget.controller.choreographer.errorService.error != null) { + if (widget.controller.choreographer.errorService.error != null && + !widget.controller.choreographer.itController.willOpen) { return ChoreographerHasErrorButton( widget.controller.choreographer.errorService.error!, widget.controller.choreographer, diff --git a/lib/pangea/chat/widgets/chat_input_bar.dart b/lib/pangea/chat/widgets/chat_input_bar.dart index ae13b985c..37fb3d397 100644 --- a/lib/pangea/chat/widgets/chat_input_bar.dart +++ b/lib/pangea/chat/widgets/chat_input_bar.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:fluffychat/config/themes.dart'; @@ -22,17 +24,27 @@ class ChatInputBar extends StatefulWidget { } class ChatInputBarState extends State { - void updateHeight() { - final renderBox = context.findRenderObject() as RenderBox?; - if (renderBox == null || !renderBox.hasSize) return; - widget.controller.updateInputBarHeight(renderBox.size.height); + Timer? _debounceTimer; + + void _updateHeight() { + _debounceTimer = Timer(const Duration(milliseconds: 100), () { + final renderBox = context.findRenderObject() as RenderBox?; + if (renderBox == null || !renderBox.hasSize) return; + widget.controller.updateInputBarHeight(renderBox.size.height); + }); + } + + @override + void dispose() { + _debounceTimer?.cancel(); + super.dispose(); } @override Widget build(BuildContext context) { return NotificationListener( onNotification: (SizeChangedLayoutNotification notification) { - WidgetsBinding.instance.addPostFrameCallback((_) => updateHeight()); + WidgetsBinding.instance.addPostFrameCallback((_) => _updateHeight()); return true; }, child: SizeChangedLayoutNotifier( diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index 301fda833..9be056e3a 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -13,7 +13,6 @@ import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart'; import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart'; import 'package:fluffychat/pangea/choreographer/models/it_step.dart'; import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart'; -import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart'; import 'package:fluffychat/pangea/choreographer/utils/input_paste_listener.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart'; @@ -21,7 +20,6 @@ import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/utils/overlay.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_model.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/events/repo/token_api_models.dart'; @@ -148,56 +146,57 @@ class Choreographer { ) : null; - final detectionResp = await LanguageDetectionRepo.get( - MatrixState.pangeaController.userController.accessToken, - request: LanguageDetectionRequest( + PangeaMessageTokens? tokensSent; + PangeaRepresentation? originalSent; + try { + TokensResponseModel? res; + if (l1LangCode != null && l2LangCode != null) { + res = await pangeaController.messageData + .getTokens( + repEventId: null, + room: chatController.room, + req: TokensRequestModel( + fullText: currentText, + senderL1: l1LangCode!, + senderL2: l2LangCode!, + ), + ) + .timeout(const Duration(seconds: 10)); + } + + originalSent = PangeaRepresentation( + langCode: res?.detections.firstOrNull?.langCode ?? + LanguageKeys.unknownLanguage, text: currentText, - senderl1: l1LangCode, - senderl2: l2LangCode, - ), - ); - final detections = detectionResp.detections; - final detectedLanguage = - detections.firstOrNull?.langCode ?? LanguageKeys.unknownLanguage; - - final PangeaRepresentation originalSent = PangeaRepresentation( - langCode: detectedLanguage, - text: currentText, - originalSent: true, - originalWritten: originalWritten == null, - ); + originalSent: true, + originalWritten: originalWritten == null, + ); - List? res; - if (l1LangCode != null && l2LangCode != null) { - res = await pangeaController.messageData.getTokens( - repEventId: null, - room: chatController.room, - req: TokensRequestModel( - fullText: currentText, - langCode: detectedLanguage, - senderL1: l1LangCode!, - senderL2: l2LangCode!, - ), + tokensSent = res != null + ? PangeaMessageTokens( + tokens: res.tokens, + detections: res.detections, + ) + : null; + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + "currentText": currentText, + "l1LangCode": l1LangCode, + "l2LangCode": l2LangCode, + "choreoRecord": choreoRecord.toJson(), + }, ); + } finally { + chatController.send( + originalSent: originalSent, + tokensSent: tokensSent, + choreo: choreoRecord, + ); + clear(); } - - final PangeaMessageTokens? tokensSent = res != null - ? PangeaMessageTokens( - tokens: res, - detections: detections, - ) - : null; - - chatController.send( - // originalWritten: originalWritten, - originalSent: originalSent, - tokensSent: tokensSent, - //TODO - save originalwritten tokens - // choreo: applicableChoreo, - choreo: choreoRecord, - ); - - clear(); } _resetDebounceTimer() { diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index cecf03ea0..76eaed5cb 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -104,7 +104,9 @@ class IgcController { } final IGCTextData igcTextDataResponse = - await _igcTextDataCache[reqBody.hashCode]!.data; + await _igcTextDataCache[reqBody.hashCode]! + .data + .timeout((const Duration(seconds: 10))); // this will happen when the user changes the input while igc is fetching results if (igcTextDataResponse.originalInput != choreographer.currentText) { diff --git a/lib/pangea/choreographer/controllers/it_controller.dart b/lib/pangea/choreographer/controllers/it_controller.dart index 93fa51623..8dfcca3de 100644 --- a/lib/pangea/choreographer/controllers/it_controller.dart +++ b/lib/pangea/choreographer/controllers/it_controller.dart @@ -57,7 +57,7 @@ class ITController { choreographer.setState(); } - Duration get animationSpeed => const Duration(milliseconds: 500); + Duration get animationSpeed => const Duration(milliseconds: 300); Future initializeIT(ITStartData itStartData) async { _willOpen = true; @@ -136,7 +136,8 @@ class ITController { // During first IT step, next step will not be set if (nextITStep == null) { - final ITResponseModel res = await _customInputTranslation(currentText); + final ITResponseModel res = await _customInputTranslation(currentText) + .timeout(const Duration(seconds: 10)); if (sourceText == null) return; if (res.goldContinuances != null && res.goldContinuances!.isNotEmpty) { diff --git a/lib/pangea/choreographer/widgets/it_bar.dart b/lib/pangea/choreographer/widgets/it_bar.dart index 085156c54..b5b704d08 100644 --- a/lib/pangea/choreographer/widgets/it_bar.dart +++ b/lib/pangea/choreographer/widgets/it_bar.dart @@ -7,7 +7,6 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/it_controller.dart'; -import 'package:fluffychat/pangea/choreographer/widgets/it_bar_buttons.dart'; import 'package:fluffychat/pangea/choreographer/widgets/it_feedback_card.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/instructions/instructions_enum.dart'; @@ -206,12 +205,14 @@ class ITBarState extends State with SingleTickerProviderStateMixin { if (!itController.isEditingSourceText) Padding( padding: const EdgeInsets.only(top: 8.0), - child: itController.sourceText != null - ? Text( - itController.sourceText!, - textAlign: TextAlign.center, - ) - : const LinearProgressIndicator(), + child: !itController.willOpen + ? const SizedBox() + : itController.sourceText != null + ? Text( + itController.sourceText!, + textAlign: TextAlign.center, + ) + : const LinearProgressIndicator(), ), const SizedBox(height: 8.0), Container( @@ -391,10 +392,12 @@ class ITChoices extends StatelessWidget { return const SizedBox(); } if (controller.currentITStep == null) { - return CircularProgressIndicator( - strokeWidth: 2.0, - color: Theme.of(context).colorScheme.primary, - ); + return controller.willOpen + ? CircularProgressIndicator( + strokeWidth: 2.0, + color: Theme.of(context).colorScheme.primary, + ) + : const SizedBox(); } return ChoicesArray( id: controller.currentITStep.hashCode.toString(), @@ -438,23 +441,38 @@ class ITError extends StatelessWidget { final ErrorCopy errorCopy = ErrorCopy(context, error); return Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 300), - child: Text( - // Text( - "${errorCopy.title}\n${errorCopy.body}", - // Haga clic en su mensaje para ver los significados de las palabras. - style: TextStyle( - fontStyle: FontStyle.italic, + child: RichText( + text: TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Icon( + Icons.error_outline, + size: 20, color: Theme.of(context).colorScheme.error, ), ), + TextSpan(text: " ${errorCopy.title} "), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: IconButton( + onPressed: () { + controller.closeIT(); + controller.choreographer.errorService.resetError(); + }, + icon: const Icon( + Icons.close, + size: 20, + ), + ), + ), + ], + style: TextStyle( + fontStyle: FontStyle.italic, + color: Theme.of(context).colorScheme.error, ), - ITRestartButton(controller: controller), - ], + ), + textAlign: TextAlign.center, ), ); } diff --git a/lib/pangea/choreographer/widgets/it_bar_buttons.dart b/lib/pangea/choreographer/widgets/it_bar_buttons.dart deleted file mode 100644 index bcc7704d7..000000000 --- a/lib/pangea/choreographer/widgets/it_bar_buttons.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../controllers/it_controller.dart'; - -class ITRestartButton extends StatelessWidget { - ITRestartButton({ - super.key, - required this.controller, - }); - - final ITController controller; - final PangeaController pangeaController = MatrixState.pangeaController; - - @override - Widget build(BuildContext context) { - return IconButton( - onPressed: () async { - controller.choreographer.errorService.resetError(); - controller.currentITStep = null; - controller.choreographer.getLanguageHelp(); - }, - icon: const Icon(Icons.refresh_outlined), - ); - } -} diff --git a/lib/pangea/events/controllers/message_data_controller.dart b/lib/pangea/events/controllers/message_data_controller.dart index 1421b187d..bfdee9313 100644 --- a/lib/pangea/events/controllers/message_data_controller.dart +++ b/lib/pangea/events/controllers/message_data_controller.dart @@ -12,7 +12,6 @@ import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart'; -import 'package:fluffychat/pangea/events/models/pangea_token_model.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/events/repo/token_api_models.dart'; @@ -23,7 +22,7 @@ import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; class MessageDataController extends BaseController { late PangeaController _pangeaController; - final Map>> _tokensCache = {}; + final Map> _tokensCache = {}; final Map> _representationCache = {}; late Timer _cacheTimer; @@ -54,7 +53,7 @@ class MessageDataController extends BaseController { /// get tokens from the server /// if repEventId is not null, send the tokens to the room - Future> _getTokens({ + Future _getTokens({ required String? repEventId, required TokensRequestModel req, required Room? room, @@ -83,13 +82,13 @@ class MessageDataController extends BaseController { ); } - return res.tokens; + return res; } /// get tokens from the server /// first check if the tokens are in the cache /// if repEventId is not null, send the tokens to the room - Future> getTokens({ + Future getTokens({ required String? repEventId, required TokensRequestModel req, required Room? room, diff --git a/lib/pangea/events/event_wrappers/pangea_representation_event.dart b/lib/pangea/events/event_wrappers/pangea_representation_event.dart index 27431695b..7e10fd14b 100644 --- a/lib/pangea/events/event_wrappers/pangea_representation_event.dart +++ b/lib/pangea/events/event_wrappers/pangea_representation_event.dart @@ -162,7 +162,7 @@ class RepresentationEvent { ), ); } - final List res = + final TokensResponseModel res = await MatrixState.pangeaController.messageData.getTokens( repEventId: _event?.eventId, room: _event?.room ?? parentMessageEvent.room, @@ -180,7 +180,7 @@ class RepresentationEvent { ), ); - return res; + return res.tokens; } Future sendTokensEvent( diff --git a/lib/pangea/events/repo/token_api_models.dart b/lib/pangea/events/repo/token_api_models.dart index cbd0035b5..2c3744454 100644 --- a/lib/pangea/events/repo/token_api_models.dart +++ b/lib/pangea/events/repo/token_api_models.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/pangea/choreographer/models/language_detection_model.dart'; import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/events/models/pangea_token_model.dart'; +import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; class TokensRequestModel { /// the text to be tokenized @@ -24,16 +25,16 @@ class TokensRequestModel { TokensRequestModel({ required this.fullText, - required this.langCode, required this.senderL1, required this.senderL2, + this.langCode, }); Map toJson() => { ModelKey.fullText: fullText, ModelKey.userL1: senderL1, ModelKey.userL2: senderL2, - ModelKey.langCode: langCode, + ModelKey.langCode: langCode ?? LanguageKeys.unknownLanguage, }; // override equals and hashcode