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.
210 lines
6.9 KiB
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)));
|
|
}
|
|
}
|