You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fluffychat/lib/pangea/choreographer/controllers/igc_controller.dart

210 lines
6.9 KiB
Dart

import 'dart:async';
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';
import '../../utils/overlay.dart';
class IgcController {
Choreographer choreographer;
IGCTextData? igcTextData;
Object? igcError;
Completer<IGCTextData> igcCompleter = Completer();
late SpanDataController spanDataController;
IgcController(this.choreographer) {
spanDataController = SpanDataController(choreographer);
}
Future<void> getIGCTextData({
required bool onlyTokensAndLanguageDetection,
}) async {
try {
if (choreographer.currentText.isEmpty) return clear();
debugPrint('getIGCTextData called with ${choreographer.currentText}');
debugPrint(
'getIGCTextData called with tokensOnly = $onlyTokensAndLanguageDetection',
);
final IGCRequestBody reqBody = IGCRequestBody(
fullText: choreographer.currentText,
userId: choreographer.pangeaController.userController.userId!,
userL1: choreographer.l1LangCode!,
userL2: choreographer.l2LangCode!,
enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection,
enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection,
prevMessages: prevMessages(),
);
final IGCTextData igcTextDataResponse = await IgcRepo.getIGC(
choreographer.accessToken,
igcRequest: reqBody,
);
// this will happen when the user changes the input while igc is fetching results
if (igcTextDataResponse.originalInput != choreographer.currentText) {
return;
}
igcTextData = igcTextDataResponse;
// TODO - for each new match,
// check if existing igcTextData has one and only one match with the same error text and correction
// if so, keep the original match and discard the new one
// if not, add the new match to the existing igcTextData
// After fetching igc data, pre-call span details for each match optimistically.
// This will make the loading of span details faster for the user
if (igcTextData?.matches.isNotEmpty ?? false) {
for (int i = 0; i < igcTextData!.matches.length; i++) {
spanDataController.getSpanDetails(i);
}
}
debugPrint("igc text ${igcTextData.toString()}");
} catch (err, stack) {
debugger(when: kDebugMode);
choreographer.errorService.setError(
ChoreoError(type: ChoreoErrorType.unknown, raw: err),
);
ErrorHandler.logError(e: err, s: stack);
clear();
}
}
void showFirstMatch(BuildContext context) {
if (igcTextData == null || igcTextData!.matches.isEmpty) {
debugger(when: kDebugMode);
ErrorHandler.logError(
m: "should not be calling showFirstMatch with this igcTextData ${igcTextData?.toJson().toString()}",
s: StackTrace.current,
);
return;
}
const int firstMatchIndex = 0;
final PangeaMatch match = igcTextData!.matches[firstMatchIndex];
if (match.isITStart &&
choreographer.itAutoPlayEnabled &&
igcTextData != null) {
choreographer.onITStart(igcTextData!.matches[firstMatchIndex]);
return;
}
choreographer.chatController.inputFocus.unfocus();
OverlayUtil.showPositionedCard(
context: context,
cardToShow: SpanCard(
scm: SpanCardModel(
matchIndex: firstMatchIndex,
onReplacementSelect: choreographer.onReplacementSelect,
onSentenceRewrite: (value) async {},
onIgnore: () => choreographer.onIgnoreMatch(
cursorOffset: match.match.offset,
),
onITStart: () {
if (choreographer.itEnabled && igcTextData != null) {
choreographer.onITStart(igcTextData!.matches[firstMatchIndex]);
}
},
choreographer: choreographer,
),
roomId: choreographer.roomId,
),
maxHeight: match.isITStart ? 260 : 350,
maxWidth: 350,
transformTargetId: choreographer.inputTransformTargetKey,
);
}
/// Get the content of previous text and audio messages in chat.
/// Passed to IGC request to add context.
List<PreviousMessage> prevMessages({int numMessages = 5}) {
final List<Event> events = choreographer.chatController.visibleEvents
.where(
(e) =>
e.type == EventTypes.Message &&
(e.messageType == MessageTypes.Text ||
e.messageType == MessageTypes.Audio),
)
.toList();
final List<PreviousMessage> 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;
if (igcTextData!.originalInput != choreographer.currentText) {
debugPrint(
"returning isIGCTextDataRelevant false because text has changed",
);
return false;
}
return true;
}
clear() {
igcTextData = null;
spanDataController.clearCache();
// Not sure why this is here
// MatrixState.pAnyState.closeOverlay();
}
bool get canSendMessage {
if (choreographer.isFetching) return false;
if (igcTextData == null ||
choreographer.errorService.isError ||
igcTextData!.matches.isEmpty) {
return true;
}
return !((choreographer.itEnabled &&
igcTextData!.matches.any((match) => match.isITStart)) ||
(choreographer.igcEnabled &&
igcTextData!.matches.any((match) => !match.isITStart)));
}
}