diff --git a/lib/pangea/choreographer/controllers/igc_controller.dart b/lib/pangea/choreographer/controllers/igc_controller.dart index ab0e48669..237697b40 100644 --- a/lib/pangea/choreographer/controllers/igc_controller.dart +++ b/lib/pangea/choreographer/controllers/igc_controller.dart @@ -4,12 +4,14 @@ import 'dart:developer'; import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart'; import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller.dart'; +import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart'; import 'package:fluffychat/pangea/models/igc_text_data_model.dart'; import 'package:fluffychat/pangea/models/pangea_match_model.dart'; import 'package:fluffychat/pangea/repo/igc_repo.dart'; import 'package:fluffychat/pangea/widgets/igc/span_card.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:matrix/matrix.dart'; import '../../models/span_card_model.dart'; import '../../utils/error_handler.dart'; @@ -43,6 +45,7 @@ class IgcController { userL2: choreographer.l2LangCode!, enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection, enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection, + prevMessages: prevMessages(), ); final IGCTextData igcTextDataResponse = await IgcRepo.getIGC( @@ -125,6 +128,49 @@ class IgcController { ); } + /// Get the content of previous text and audio messages in chat. + /// Passed to IGC request to add context. + List prevMessages({int numMessages = 5}) { + final List events = choreographer.chatController.visibleEvents + .where( + (e) => + e.type == EventTypes.Message && + (e.messageType == MessageTypes.Text || + e.messageType == MessageTypes.Audio), + ) + .toList(); + + final List messages = []; + for (final Event event in events) { + final String? content = event.messageType == MessageTypes.Text + ? event.content.toString() + : PangeaMessageEvent( + event: event, + timeline: choreographer.chatController.timeline!, + ownMessage: event.senderId == + choreographer.pangeaController.matrixState.client.userID, + ) + .getSpeechToTextLocal( + choreographer.l1LangCode, + choreographer.l2LangCode, + ) + ?.transcript + .text; + if (content == null) continue; + messages.add( + PreviousMessage( + content: content, + sender: event.senderId, + timestamp: event.originServerTs, + ), + ); + if (messages.length >= numMessages) { + return messages; + } + } + return messages; + } + bool get hasRelevantIGCTextData { if (igcTextData == null) return false; diff --git a/lib/pangea/constants/model_keys.dart b/lib/pangea/constants/model_keys.dart index b42061446..195e919b4 100644 --- a/lib/pangea/constants/model_keys.dart +++ b/lib/pangea/constants/model_keys.dart @@ -69,6 +69,10 @@ class ModelKey { static const String permissions = "permissions"; static const String enableIGC = "enable_igc"; static const String enableIT = "enable_it"; + static const String prevMessages = "prev_messages"; + static const String prevContent = "prev_content"; + static const String prevSender = "prev_sender"; + static const String prevTimestamp = "prev_timestamp"; static const String originalSent = "original_sent"; static const String originalWritten = "original_written"; diff --git a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart index f1c9e5082..cd4ce5e76 100644 --- a/lib/pangea/matrix_event_wrappers/pangea_message_event.dart +++ b/lib/pangea/matrix_event_wrappers/pangea_message_event.dart @@ -269,6 +269,21 @@ class PangeaMessageEvent { null; }).toSet(); + SpeechToTextModel? getSpeechToTextLocal( + String? l1Code, + String? l2Code, + ) { + if (l1Code == null || l2Code == null) { + return null; + } + return representations + .firstWhereOrNull( + (element) => element.content.speechToText != null, + ) + ?.content + .speechToText; + } + Future getSpeechToText( String l1Code, String l2Code, diff --git a/lib/pangea/repo/igc_repo.dart b/lib/pangea/repo/igc_repo.dart index e32f6cab7..33abb4deb 100644 --- a/lib/pangea/repo/igc_repo.dart +++ b/lib/pangea/repo/igc_repo.dart @@ -94,12 +94,42 @@ class IgcRepo { } } +/// Previous text/audio message sent in chat +/// Contain message content, sender, and timestamp +class PreviousMessage { + String content; + String sender; + DateTime timestamp; + + PreviousMessage({ + required this.content, + required this.sender, + required this.timestamp, + }); + + factory PreviousMessage.fromJson(Map json) => + PreviousMessage( + content: json[ModelKey.prevContent] ?? "", + sender: json[ModelKey.prevSender] ?? "", + timestamp: json[ModelKey.prevTimestamp] == null + ? DateTime.now() + : DateTime.parse(json[ModelKey.prevTimestamp]), + ); + + Map toJson() => { + ModelKey.prevContent: content, + ModelKey.prevSender: sender, + ModelKey.prevTimestamp: timestamp.toIso8601String(), + }; +} + class IGCRequestBody { String fullText; String userL1; String userL2; bool enableIT; bool enableIGC; + List prevMessages; IGCRequestBody({ required this.fullText, @@ -107,6 +137,7 @@ class IGCRequestBody { required this.userL2, required this.enableIGC, required this.enableIT, + required this.prevMessages, }); Map toJson() => { @@ -115,5 +146,7 @@ class IGCRequestBody { ModelKey.userL2: userL2, "enable_it": enableIT, "enable_igc": enableIGC, + ModelKey.prevMessages: + jsonEncode(prevMessages.map((x) => x.toJson()).toList()), }; } diff --git a/lib/pangea/widgets/chat/message_toolbar.dart b/lib/pangea/widgets/chat/message_toolbar.dart index 3c63c81fc..7c5682933 100644 --- a/lib/pangea/widgets/chat/message_toolbar.dart +++ b/lib/pangea/widgets/chat/message_toolbar.dart @@ -59,8 +59,12 @@ class ToolbarDisplayController { } void showToolbar(BuildContext context, {MessageMode? mode}) { - // Close keyboard, if open - FocusManager.instance.primaryFocus?.unfocus(); + // Don't show toolbar if keyboard open + if (controller.inputFocus.hasFocus) { + FocusManager.instance.primaryFocus?.unfocus(); + return; + } + bool toolbarUp = true; if (highlighted) return; if (controller.selectMode) { @@ -86,13 +90,12 @@ class ToolbarDisplayController { if (targetOffset.dy < 320) { final spaceBeneath = MediaQuery.of(context).size.height - (targetOffset.dy + transformTargetSize.height); - // If toolbar is open, opening toolbar beneath without scrolling can cause issues - // if (spaceBeneath >= 320) { - // toolbarUp = false; - // } + if (spaceBeneath >= 320) { + toolbarUp = false; + } // See if it's possible to scroll up to make space - if (controller.scrollController.offset - targetOffset.dy + 320 >= + else if (controller.scrollController.offset - targetOffset.dy + 320 >= controller.scrollController.position.minScrollExtent && controller.scrollController.offset - targetOffset.dy + 320 <= controller.scrollController.position.maxScrollExtent) {