fluffychat/lib/pangea/choreographer/controllers/it_controller.dart

302 lines
8.2 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:async/async.dart';
import 'package:fluffychat/pangea/choreographer/constants/choreo_constants.dart';
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
import 'package:fluffychat/pangea/choreographer/enums/choreo_mode.dart';
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
import 'package:fluffychat/pangea/choreographer/repo/it_repo.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import '../models/it_step.dart';
import '../repo/it_request_model.dart';
import '../repo/it_response_model.dart';
import 'choreographer.dart';
class ITController {
final Choreographer _choreographer;
ITStep? _currentITStep;
final List<Completer<ITStep>> _queue = [];
GoldRouteTracker? _goldRouteTracker;
bool _open = false;
bool _editing = false;
bool _dismissed = false;
ITController(this._choreographer);
bool get open => _open;
bool get editing => _editing;
bool get dismissed => _dismissed;
List<Continuance>? get continuances => _currentITStep?.continuances;
bool get isTranslationDone => _currentITStep?.isFinal ?? false;
String? get _sourceText => _choreographer.sourceText;
ITRequestModel _request(String textInput) {
assert(_sourceText != null);
return ITRequestModel(
text: _sourceText!,
customInput: textInput,
sourceLangCode:
MatrixState.pangeaController.languageController.activeL1Code()!,
targetLangCode:
MatrixState.pangeaController.languageController.activeL2Code()!,
userId: _choreographer.chatController.room.client.userID!,
roomId: _choreographer.chatController.room.id,
goldTranslation: _goldRouteTracker?.fullTranslation,
goldContinuances: _goldRouteTracker?.continuances,
);
}
void openIT() => _open = true;
void closeIT() {
// if the user hasn't gone through any IT steps, reset the text
if (_choreographer.currentText.isEmpty && _sourceText != null) {
_choreographer.textController.setSystemText(
_sourceText!,
EditType.itDismissed,
);
}
clear(dismissed: true);
}
void clear({bool dismissed = false}) {
MatrixState.pAnyState.closeOverlay("it_feedback_card");
_open = false;
_editing = false;
_dismissed = dismissed;
_queue.clear();
_currentITStep = null;
_goldRouteTracker = null;
_choreographer.setChoreoMode(ChoreoMode.igc);
_choreographer.setSourceText(null);
}
void setEditing(bool value) => _editing = value;
void onSubmitEdits() {
_editing = false;
_queue.clear();
_currentITStep = null;
_goldRouteTracker = null;
continueIT();
}
Continuance onSelectContinuance(int index) {
if (_currentITStep == null) {
throw "onSelectContinuance called with null currentITStep";
}
if (index < 0 || index >= _currentITStep!.continuances.length) {
throw "onSelectContinuance called with invalid index $index";
}
final step = _currentITStep!.continuances[index];
_currentITStep!.continuances[index] = step.copyWith(
wasClicked: true,
);
return _currentITStep!.continuances[index];
}
CompletedITStep getAcceptedITStep(int chosenIndex) {
if (_currentITStep == null) {
throw "getAcceptedITStep called with null currentITStep";
}
if (chosenIndex < 0 || chosenIndex >= _currentITStep!.continuances.length) {
throw "getAcceptedITStep called with invalid index $chosenIndex";
}
return CompletedITStep(
_currentITStep!.continuances,
chosen: chosenIndex,
);
}
Future<void> continueIT() async {
if (_currentITStep == null) {
await _initTranslationData();
return;
}
if (_queue.isEmpty) {
_choreographer.closeIT();
return;
}
final nextStepCompleter = _queue.removeAt(0);
try {
_currentITStep = await nextStepCompleter.future;
} catch (e) {
if (_open) {
_choreographer.errorService.setErrorAndLock(
ChoreoError(raw: e),
);
}
}
}
Future<void> _initTranslationData() async {
final String currentText = _choreographer.currentText;
final res = await ITRepo.get(_request(currentText)).timeout(
const Duration(seconds: 10),
onTimeout: () {
return Result.error(
TimeoutException("ITRepo.get timed out after 10 seconds"),
);
},
);
if (_sourceText == null || !_open) return;
if (res.isError || res.result?.goldContinuances == null) {
_choreographer.errorService.setErrorAndLock(
ChoreoError(raw: res.asError),
);
return;
}
final result = res.result!;
_goldRouteTracker = GoldRouteTracker(
result.goldContinuances!,
_sourceText!,
);
_currentITStep = ITStep(
sourceText: _sourceText!,
currentText: currentText,
responseModel: res.result!,
storedGoldContinuances: _goldRouteTracker!.continuances,
);
_fillITStepQueue();
}
Future<void> _fillITStepQueue() async {
if (_sourceText == null || _goldRouteTracker!.continuances.length < 2) {
return;
}
final sourceText = _sourceText!;
String currentText =
_choreographer.currentText + _goldRouteTracker!.continuances[0].text;
for (int i = 1; i < _goldRouteTracker!.continuances.length; i++) {
_queue.add(Completer<ITStep>());
final res = await ITRepo.get(_request(currentText)).timeout(
const Duration(seconds: 10),
onTimeout: () {
return Result.error(
TimeoutException("ITRepo.get timed out after 10 seconds"),
);
},
);
if (res.isError) {
_queue.last.completeError(res.asError!);
break;
} else {
final step = ITStep(
sourceText: sourceText,
currentText: currentText,
responseModel: res.result!,
storedGoldContinuances: _goldRouteTracker!.continuances,
);
_queue.last.complete(step);
}
currentText += _goldRouteTracker!.continuances[i].text;
}
}
}
class GoldRouteTracker {
final String _originalText;
final List<Continuance> continuances;
const GoldRouteTracker(this.continuances, String originalText)
: _originalText = originalText;
Continuance? currentContinuance({
required String currentText,
required String sourceText,
}) {
if (_originalText != sourceText) {
debugPrint("$_originalText != $_originalText");
return null;
}
String stack = "";
for (final cont in continuances) {
if (stack == currentText) {
return cont;
}
stack += cont.text;
}
return null;
}
String? get fullTranslation {
if (continuances.isEmpty) return null;
String full = "";
for (final cont in continuances) {
full += cont.text;
}
return full;
}
}
class ITStep {
late List<Continuance> continuances;
late bool isFinal;
ITStep({
required String sourceText,
required String currentText,
required ITResponseModel responseModel,
required List<Continuance>? storedGoldContinuances,
}) {
final List<Continuance> gold =
storedGoldContinuances ?? responseModel.goldContinuances ?? [];
final goldTracker = GoldRouteTracker(gold, sourceText);
isFinal = responseModel.isFinal;
if (responseModel.continuances.isEmpty) {
continuances = [];
} else {
final Continuance? goldCont = goldTracker.currentContinuance(
currentText: currentText,
sourceText: sourceText,
);
if (goldCont != null) {
continuances = [
...responseModel.continuances
.where((c) => c.text.toLowerCase() != goldCont.text.toLowerCase())
.map((e) {
//we only want one green choice and for that to be our gold
if (e.level == ChoreoConstants.levelThresholdForGreen) {
return e.copyWith(
level: ChoreoConstants.levelThresholdForYellow,
);
}
return e;
}),
goldCont,
];
continuances.shuffle();
} else {
continuances = List<Continuance>.from(responseModel.continuances);
}
}
}
}