feat: fetch new tokens for messages without originalsent representations (#1853)

This commit is contained in:
ggurdin 2025-02-19 15:21:18 -05:00 committed by GitHub
parent 21b9b7e720
commit 32d314c026
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 242 additions and 38 deletions

View file

@ -10,7 +10,7 @@ import 'package:fluffychat/pangea/choreographer/models/language_detection_model.
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/choreographer/models/span_card_model.dart';
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_request.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
@ -21,7 +21,7 @@ import '../../common/constants/model_keys.dart';
// import 'package:language_tool/language_tool.dart';
class IGCTextData {
LanguageDetectionRequest detections;
LanguageDetectionResponse detections;
String originalInput;
String? fullTextCorrection;
List<PangeaToken> tokens;
@ -47,14 +47,15 @@ class IGCTextData {
factory IGCTextData.fromJson(Map<String, dynamic> json) {
// changing this to allow for use of the LanguageDetectionResponse methods
// TODO - change API after we're sure all clients are updated. not urgent.
final LanguageDetectionRequest detections = json[_detectionsKey] is Iterable
? LanguageDetectionRequest.fromJson({
"detections": json[_detectionsKey],
"full_text": json["original_input"],
})
: LanguageDetectionRequest.fromJson(
json[_detectionsKey] as Map<String, dynamic>,
);
final LanguageDetectionResponse detections =
json[_detectionsKey] is Iterable
? LanguageDetectionResponse.fromJson({
"detections": json[_detectionsKey],
"full_text": json["original_input"],
})
: LanguageDetectionResponse.fromJson(
json[_detectionsKey] as Map<String, dynamic>,
);
return IGCTextData(
tokens: (json[_tokensKey] as Iterable)
@ -102,7 +103,7 @@ class IGCTextData {
}
return IGCTextData(
detections: LanguageDetectionRequest(
detections: LanguageDetectionResponse(
detections: [
LanguageDetection(langCode: content.langCode, confidence: 1),
],
@ -136,7 +137,7 @@ class IGCTextData {
};
/// if we haven't run IGC or IT or there are no matches, we use the highest validated detection
/// from [LanguageDetectionRequest.highestValidatedDetection]
/// from [LanguageDetectionResponse.highestValidatedDetection]
/// if we have run igc/it and there are no matches, we can relax the threshold
/// and use the highest confidence detection
String get detectedLanguage {

View file

@ -4,7 +4,7 @@ import 'package:http/http.dart';
import 'package:fluffychat/pangea/choreographer/models/language_detection_model.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_request.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
import 'package:fluffychat/pangea/choreographer/repo/span_data_repo.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/events/models/pangea_token_model.dart';
@ -41,7 +41,7 @@ class IgcRepo {
await Future.delayed(const Duration(seconds: 2));
final IGCTextData igcTextData = IGCTextData(
detections: LanguageDetectionRequest(
detections: LanguageDetectionResponse(
detections: [LanguageDetection(langCode: "en", confidence: 0.99)],
fullText: "This be a sample text",
),

View file

@ -1,17 +1,68 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/choreographer/models/language_detection_model.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/learning_settings/constants/language_constants.dart';
class LanguageDetectionRepo {
static Future<LanguageDetectionResponse> get(
String? accessToken, {
required LanguageDetectionRequest request,
}) async {
final Requests req = Requests(
accessToken: accessToken,
choreoApiKey: Environment.choreoApiKey,
);
final Response res = await req.post(
url: PApiUrls.languageDetection,
body: request.toJson(),
);
final Map<String, dynamic> json =
jsonDecode(utf8.decode(res.bodyBytes).toString());
final LanguageDetectionResponse response =
LanguageDetectionResponse.fromJson(json);
return response;
}
}
class LanguageDetectionRequest {
final String text;
final String? senderl1;
final String? senderl2;
LanguageDetectionRequest({
required this.text,
this.senderl1,
this.senderl2,
});
Map<String, dynamic> toJson() {
return {
'full_text': text,
'sender_l1': senderl1,
'sender_l2': senderl2,
};
}
}
class LanguageDetectionResponse {
List<LanguageDetection> detections;
String fullText;
LanguageDetectionRequest({
LanguageDetectionResponse({
required this.detections,
required this.fullText,
});
factory LanguageDetectionRequest.fromJson(Map<String, dynamic> json) {
return LanguageDetectionRequest(
factory LanguageDetectionResponse.fromJson(Map<String, dynamic> json) {
return LanguageDetectionResponse(
detections: List<LanguageDetection>.from(
(json['detections'] as Iterable).map(
(e) => LanguageDetection.fromJson(e),

View file

@ -12,6 +12,7 @@ import 'package:fluffychat/pangea/common/controllers/base_controller.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/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';
@ -184,4 +185,70 @@ class MessageDataController extends BaseController {
return rep;
}
Future<String?> getPangeaRepresentationEvent({
required FullTextTranslationRequestModel req,
required PangeaMessageEvent messageEvent,
bool originalSent = false,
}) async {
final FullTextTranslationResponseModel res =
await FullTextTranslationRepo.translate(
accessToken: _pangeaController.userController.accessToken,
request: req,
);
if (originalSent && messageEvent.originalSent != null) {
originalSent = false;
}
final rep = PangeaRepresentation(
langCode: req.tgtLang,
text: res.bestTranslation,
originalSent: originalSent,
originalWritten: false,
);
try {
final repEvent = await messageEvent.room.sendPangeaEvent(
content: rep.toJson(),
parentEventId: messageEvent.eventId,
type: PangeaEventTypes.representation,
);
return repEvent?.eventId;
} catch (e, s) {
ErrorHandler.logError(
m: "error in _getPangeaRepresentation.sendPangeaEvent",
e: e,
s: s,
data: req.toJson(),
);
return null;
}
}
Future<void> sendTokensEvent({
required String repEventId,
required TokensRequestModel req,
required Room room,
}) async {
final TokensResponseModel res = await _fetchTokens(
_pangeaController.userController.accessToken,
req,
);
try {
await room.sendPangeaEvent(
content: PangeaMessageTokens(tokens: res.tokens).toJson(),
parentEventId: repEventId,
type: PangeaEventTypes.tokens,
);
} catch (e, s) {
ErrorHandler.logError(
m: "error in _getTokens.sendPangeaEvent",
e: e,
s: s,
data: req.toJson(),
);
}
}
}

View file

@ -10,6 +10,7 @@ import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
import 'package:fluffychat/pangea/choreographer/repo/full_text_translation_repo.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
import 'package:fluffychat/pangea/events/event_wrappers/pangea_representation_event.dart';
import 'package:fluffychat/pangea/events/extensions/pangea_event_extension.dart';
@ -508,6 +509,39 @@ class PangeaMessageEvent {
);
}
Future<String?> representationByDetectedLanguage() async {
final resp = await LanguageDetectionRepo.get(
MatrixState.pangeaController.userController.accessToken,
request: LanguageDetectionRequest(
text: _latestEdit.body,
senderl1: l1Code,
senderl2: l2Code,
),
);
final langCode = resp.detections.firstOrNull?.langCode;
if (langCode == null) return null;
if (langCode == originalSent?.langCode) {
return originalSent?.event?.eventId;
}
// clear representations cache so the new representation event can be added when next requested
_representations = null;
return MatrixState.pangeaController.messageData
.getPangeaRepresentationEvent(
req: FullTextTranslationRequestModel(
text: originalSent?.content.text ?? _latestEdit.body,
srcLang: originalSent?.langCode,
tgtLang: langCode,
userL2: l2Code ?? LanguageKeys.unknownLanguage,
userL1: l1Code ?? LanguageKeys.unknownLanguage,
),
messageEvent: this,
originalSent: true,
);
}
RepresentationEvent? get originalSent => representations
.firstWhereOrNull((element) => element.content.originalSent);

View file

@ -160,6 +160,33 @@ class RepresentationEvent {
return res;
}
Future<void> sendTokensEvent(
String repEventID,
Room room,
String userl1,
String userl2,
) async {
if (tokens != null) return;
if (_event == null) {
ErrorHandler.logError(
e: "Called getTokensEvent with no _event",
data: {},
);
return;
}
await MatrixState.pangeaController.messageData.sendTokensEvent(
repEventId: repEventID,
room: room,
req: TokensRequestModel(
fullText: text,
langCode: langCode,
senderL1: userl1,
senderL2: userl2,
),
);
}
ChoreoRecord? get choreo {
if (_choreo != null) return _choreo;

View file

@ -12,6 +12,7 @@ import 'package:fluffychat/pages/chat/chat.dart';
import 'package:fluffychat/pangea/analytics_misc/message_analytics_controller.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/events/event_wrappers/pangea_representation_event.dart';
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/toolbar/controllers/text_to_speech_controller.dart';
@ -52,8 +53,6 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
MessageMode toolbarMode = MessageMode.noneSelected;
PangeaTokenText? _selectedSpan;
List<PangeaTokenText>? _highlightedTokens;
List<PangeaToken>? tokens;
bool initialized = false;
PangeaMessageEvent? get pangeaMessageEvent => PangeaMessageEvent(
@ -143,20 +142,43 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
}
MessageAnalyticsEntry? get messageAnalyticsEntry =>
pangeaMessageEvent != null && tokens != null
pangeaMessageEvent?.messageDisplayRepresentation?.tokens != null
? MatrixState.pangeaController.getAnalytics.perMessage.get(
tokens!,
pangeaMessageEvent!.messageDisplayRepresentation!.tokens!,
pangeaMessageEvent!,
)
: null;
Future<RepresentationEvent?> _fetchNewRepEvent() async {
final RepresentationEvent? repEvent =
pangeaMessageEvent?.messageDisplayRepresentation;
if (repEvent != null) return repEvent;
final eventID =
await pangeaMessageEvent?.representationByDetectedLanguage();
if (eventID == null) return null;
final event = await widget._event.room.getEventById(eventID);
if (event == null) return null;
return RepresentationEvent(
timeline: pangeaMessageEvent!.timeline,
parentMessageEvent: pangeaMessageEvent!.event,
event: event,
);
}
Future<void> initializeTokensAndMode() async {
try {
final repEvent = pangeaMessageEvent?.messageDisplayRepresentation;
if (repEvent != null) {
tokens = await repEvent.tokensGlobal(
pangeaMessageEvent!.senderId,
pangeaMessageEvent!.originServerTs,
RepresentationEvent? repEvent =
pangeaMessageEvent?.messageDisplayRepresentation;
repEvent ??= await _fetchNewRepEvent();
if (repEvent?.event != null) {
await repEvent!.sendTokensEvent(
repEvent.event!.eventId,
widget._event.room,
MatrixState.pangeaController.languageController.userL1!.langCode,
MatrixState.pangeaController.languageController.userL2!.langCode,
);
}
} catch (e, s) {
@ -331,7 +353,9 @@ class MessageOverlayController extends State<MessageSelectionOverlay>
);
}
PangeaToken? get selectedToken => tokens?.firstWhereOrNull(isTokenSelected);
PangeaToken? get selectedToken =>
pangeaMessageEvent?.messageDisplayRepresentation?.tokens
?.firstWhereOrNull(isTokenSelected);
/// Whether the overlay is currently displaying a selection
bool get isSelection => _selectedSpan != null || _highlightedTokens != null;

View file

@ -16,9 +16,6 @@ import 'package:fluffychat/widgets/matrix.dart';
/// Need to test.
class MessageTokenText extends StatelessWidget {
final PangeaMessageEvent _pangeaMessageEvent;
final List<PangeaToken>? _tokens;
final TextStyle _style;
final bool Function(PangeaToken)? _isSelected;
@ -34,9 +31,11 @@ class MessageTokenText extends StatelessWidget {
}) : _onClick = onClick,
_isSelected = isSelected,
_style = style,
_tokens = tokens,
_pangeaMessageEvent = pangeaMessageEvent;
List<PangeaToken>? get _tokens =>
_pangeaMessageEvent.messageDisplayRepresentation?.tokens;
MessageAnalyticsEntry? get messageAnalyticsEntry => _tokens != null
? MatrixState.pangeaController.getAnalytics.perMessage.get(
_tokens!,
@ -44,6 +43,12 @@ class MessageTokenText extends StatelessWidget {
)
: null;
void callOnClick(TokenPosition tokenPosition) {
_onClick != null && tokenPosition.token != null
? _onClick!(tokenPosition.token!)
: null;
}
@override
Widget build(BuildContext context) {
if (_tokens == null) {
@ -53,12 +58,6 @@ class MessageTokenText extends StatelessWidget {
);
}
void callOnClick(TokenPosition tokenPosition) {
_onClick != null && tokenPosition.token != null
? _onClick!(tokenPosition.token!)
: null;
}
return MessageTextWidget(
pangeaMessageEvent: _pangeaMessageEvent,
style: _style,

View file

@ -200,7 +200,8 @@ class PracticeActivityCardState extends State<PracticeActivityCard> {
userL1: MatrixState.pangeaController.languageController.userL1!.langCode,
userL2: MatrixState.pangeaController.languageController.userL2!.langCode,
messageText: widget.pangeaMessageEvent.messageDisplayText,
messageTokens: widget.overlayController.tokens!,
messageTokens:
widget.pangeaMessageEvent.messageDisplayRepresentation?.tokens ?? [],
activityQualityFeedback: activityFeedback,
targetTokens: tokens,
targetType: type,