improving documentation

This commit is contained in:
William Jordan-Cooley 2024-06-30 10:36:09 -04:00
parent 923d39eab6
commit 919cfc4bd3
9 changed files with 181 additions and 149 deletions

View file

@ -183,6 +183,7 @@ class Choreographer {
_textController.setSystemText("", EditType.itStart);
}
/// Handles any changes to the text input
_onChangeListener() {
if (_noChange) {
return;
@ -191,21 +192,26 @@ class Choreographer {
if ([
EditType.igc,
].contains(_textController.editType)) {
// this may be unnecessary now that tokens are not used
// to allow click of words in the input field and we're getting this at the end
// TODO - turn it off and tested that this is fine
igc.justGetTokensAndAddThemToIGCTextData();
// we set editType to keyboard here because that is the default for it
// and we want to make sure that the next change is treated as a keyboard change
// unless the system explicity sets it to something else. this
textController.editType = EditType.keyboard;
return;
}
// not sure if this is necessary now
MatrixState.pAnyState.closeOverlay();
if (errorService.isError) {
return;
}
// if (igc.igcTextData != null) {
igc.clear();
// setState();
// }
_resetDebounceTimer();
@ -215,7 +221,9 @@ class Choreographer {
() => getLanguageHelp(),
);
} else {
getLanguageHelp(ChoreoMode.it == choreoMode);
getLanguageHelp(
onlyTokensAndLanguageDetection: ChoreoMode.it == choreoMode,
);
}
//Note: we don't set the keyboard type on each keyboard stroke so this is how we default to
@ -224,10 +232,14 @@ class Choreographer {
textController.editType = EditType.keyboard;
}
Future<void> getLanguageHelp([
bool tokensOnly = false,
/// Fetches the language help for the current text, including grammar correction, language detection,
/// tokens, and translations. Includes logic to exit the flow if the user is not subscribed, if the tools are not enabled, or
/// or if autoIGC is not enabled and the user has not manually requested it.
/// [onlyTokensAndLanguageDetection] will
Future<void> getLanguageHelp({
bool onlyTokensAndLanguageDetection = false,
bool manual = false,
]) async {
}) async {
try {
if (errorService.isError) return;
final CanSendStatus canSendStatus =
@ -242,13 +254,15 @@ class Choreographer {
startLoading();
if (choreoMode == ChoreoMode.it &&
itController.isTranslationDone &&
!tokensOnly) {
!onlyTokensAndLanguageDetection) {
// debugger(when: kDebugMode);
}
await (choreoMode == ChoreoMode.it && !itController.isTranslationDone
? itController.getTranslationData(_useCustomInput)
: igc.getIGCTextData(tokensOnly: tokensOnly));
: igc.getIGCTextData(
onlyTokensAndLanguageDetection: onlyTokensAndLanguageDetection,
));
} catch (err, stack) {
ErrorHandler.logError(e: err, s: stack);
} finally {
@ -494,8 +508,9 @@ class Choreographer {
// TODO - this is a bit of a hack, and should be tested more
// we should also check that user has not done customInput
if (itController.completedITSteps.isNotEmpty && itController.allCorrect)
if (itController.completedITSteps.isNotEmpty && itController.allCorrect) {
return l2LangCode!;
}
return null;
}
@ -533,9 +548,11 @@ class Choreographer {
chatController.room,
);
bool get itAutoPlayEnabled => pangeaController.pStoreService.read(
bool get itAutoPlayEnabled =>
pangeaController.pStoreService.read(
MatrixProfile.itAutoPlay.title,
) ?? false;
) ??
false;
bool get definitionsEnabled =>
pangeaController.permissionsController.isToolEnabled(

View file

@ -3,7 +3,7 @@ import 'dart:developer';
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
import 'package:fluffychat/pangea/controllers/span_data_controller.dart';
import 'package:fluffychat/pangea/choreographer/controllers/span_data_controller.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';
@ -29,59 +29,64 @@ class IgcController {
spanDataController = SpanDataController(choreographer);
}
Future<void> getIGCTextData({required bool tokensOnly}) async {
Future<void> getIGCTextData({
required bool onlyTokensAndLanguageDetection,
}) async {
try {
if (choreographer.currentText.isEmpty) return clear();
// the error spans are going to be reloaded, so clear the cache
// @ggurdin: Why is this separate from the clear() call?
// Also, if the spans are equal according the to the equals method, why not reuse the cached span data?
// It seems this would save some calls if the user makes some tiny changes to the text that don't
// change the matches at all.
spanDataController.clearCache();
debugPrint('getIGCTextData called with ${choreographer.currentText}');
debugPrint('getIGCTextData called with tokensOnly = $tokensOnly');
debugPrint(
'getIGCTextData called with tokensOnly = $onlyTokensAndLanguageDetection',
);
final IGCRequestBody reqBody = IGCRequestBody(
fullText: choreographer.currentText,
userL1: choreographer.l1LangCode!,
userL2: choreographer.l2LangCode!,
enableIGC: choreographer.igcEnabled && !tokensOnly,
enableIT: choreographer.itEnabled && !tokensOnly,
tokensOnly: tokensOnly,
enableIGC: choreographer.igcEnabled && !onlyTokensAndLanguageDetection,
enableIT: choreographer.itEnabled && !onlyTokensAndLanguageDetection,
);
final IGCTextData igcTextDataResponse = await IgcRepo.getIGC(
await choreographer.accessToken,
igcRequest: reqBody,
);
// temp fix
igcTextDataResponse.originalInput = reqBody.fullText;
//this will happen when the user changes the input while igc is fetching results
// this will happen when the user changes the input while igc is fetching results
if (igcTextDataResponse.originalInput != choreographer.currentText) {
// final current = choreographer.currentText;
// final igctext = igcTextDataResponse.originalInput;
// Sentry.addBreadcrumb(
// Breadcrumb(message: "igc return input does not match current text"),
// );
// debugger(when: kDebugMode);
return;
}
//TO-DO: in api call, specify turning off IT and/or grammar checking
if (!choreographer.igcEnabled) {
igcTextDataResponse.matches = igcTextDataResponse.matches
.where((match) => !match.isGrammarMatch)
.toList();
}
if (!choreographer.itEnabled) {
igcTextDataResponse.matches = igcTextDataResponse.matches
.where((match) => !match.isOutOfTargetMatch)
.toList();
}
if (!choreographer.itEnabled && !choreographer.igcEnabled) {
igcTextDataResponse.matches = [];
}
// UPDATE: This is now done in the API call. New TODO is to test this.
// if (!choreographer.igcEnabled) {
// igcTextDataResponse.matches = igcTextDataResponse.matches
// .where((match) => !match.isGrammarMatch)
// .toList();
// }
// if (!choreographer.itEnabled) {
// igcTextDataResponse.matches = igcTextDataResponse.matches
// .where((match) => !match.isOutOfTargetMatch)
// .toList();
// }
// if (!choreographer.itEnabled && !choreographer.igcEnabled) {
// igcTextDataResponse.matches = [];
// }
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) {
@ -170,11 +175,9 @@ class IgcController {
const int firstMatchIndex = 0;
final PangeaMatch match = igcTextData!.matches[firstMatchIndex];
if (
match.isITStart &&
if (match.isITStart &&
choreographer.itAutoPlayEnabled &&
igcTextData != null
) {
igcTextData != null) {
choreographer.onITStart(igcTextData!.matches[firstMatchIndex]);
return;
}

View file

@ -72,6 +72,7 @@ class ITController {
/// if IGC isn't positive that text is full L1 then translate to L1
Future<void> _setSourceText() async {
debugger(when: kDebugMode);
// try {
if (_itStartData == null || _itStartData!.text.isEmpty) {
Sentry.addBreadcrumb(
@ -167,7 +168,7 @@ class ITController {
if (isTranslationDone) {
choreographer.altTranslator.setTranslationFeedback();
choreographer.getLanguageHelp(true);
choreographer.getLanguageHelp(onlyTokensAndLanguageDetection: true);
} else {
getNextTranslationData();
}
@ -218,7 +219,6 @@ class ITController {
Future<void> onEditSourceTextSubmit(String newSourceText) async {
try {
_isOpen = true;
_isEditingSourceText = false;
_itStartData = ITStartData(newSourceText, choreographer.l1LangCode);
@ -230,7 +230,6 @@ class ITController {
_setSourceText();
getTranslationData(false);
} catch (err, stack) {
debugger(when: kDebugMode);
if (err is! http.Response) {

View file

@ -91,8 +91,8 @@ class StartIGCButtonState extends State<StartIGCButton>
if (assistanceState != AssistanceState.fetching) {
widget.controller.choreographer
.getLanguageHelp(
false,
true,
onlyTokensAndLanguageDetection: false,
manual: true,
)
.then((_) {
if (widget.controller.choreographer.igc.igcTextData != null &&

View file

@ -5,7 +5,7 @@ class Environment {
DateTime.utc(2023, 1, 25).isBefore(DateTime.now());
static String get fileName {
return ".env";
return ".local_choreo.env";
}
static bool get isStaging => synapsURL.contains("staging");

View file

@ -239,7 +239,7 @@ class MyAnalyticsController {
}
final List<List<Event>> recentMsgs =
(await Future.wait(recentMsgFutures)).toList();
final List<PracticeActivityRecordEvent> recentActivityReconds =
final List<PracticeActivityRecordEvent> recentActivityRecords =
(await Future.wait(recentActivityFutures))
.expand((e) => e)
.map((event) => PracticeActivityRecordEvent(event: event))
@ -284,14 +284,14 @@ class MyAnalyticsController {
}
// get constructs for messages
final List<OneConstructUse> constructContent = [];
final List<OneConstructUse> recentConstructUses = [];
for (final PangeaMessageEvent message in allRecentMessages) {
constructContent.addAll(message.allConstructUses);
recentConstructUses.addAll(message.allConstructUses);
}
// get constructs for practice activities
final List<Future<List<OneConstructUse>>> constructFutures = [];
for (final PracticeActivityRecordEvent activity in recentActivityReconds) {
for (final PracticeActivityRecordEvent activity in recentActivityRecords) {
final Timeline? timeline = timelineMap[activity.event.roomId!];
if (timeline == null) {
debugger(when: kDebugMode);
@ -306,13 +306,13 @@ class MyAnalyticsController {
final List<List<OneConstructUse>> constructLists =
await Future.wait(constructFutures);
constructContent.addAll(constructLists.expand((e) => e));
recentConstructUses.addAll(constructLists.expand((e) => e));
//TODO - confirm that this is the correct construct content
debugger(when: kDebugMode);
debugger(when: kDebugMode && recentConstructUses.isNotEmpty);
await analyticsRoom.sendConstructsEvent(
constructContent,
recentConstructUses,
);
}
}

View file

@ -656,106 +656,47 @@ class PangeaMessageEvent {
}
}
List<OneConstructUse> get allConstructUses =>
[...grammarConstructUses, ..._vocabUses];
/// Returns a list of [PracticeActivityEvent] for the user's active l2.
List<PracticeActivityEvent> get practiceActivities {
final String? l2code =
MatrixState.pangeaController.languageController.activeL2Code();
if (l2code == null) return [];
return practiceActivitiesByLangCode(l2code);
}
List<PracticeActivityEvent> get practiceActivities =>
l2Code == null ? [] : practiceActivitiesByLangCode(l2Code!);
// List<SpanData> get activities =>
//each match is turned into an activity that other students can access
//they're not told the answer but have to find it themselves
//the message has a blank piece which they fill in themselves
/// all construct uses for the message, including vocab and grammar
List<OneConstructUse> get allConstructUses =>
[..._grammarConstructUses, ..._vocabUses];
/// [tokens] is the final list of tokens that were sent
/// if no ga or ta,
/// make wa use for each and return
/// else
/// for each saveable vocab in the final message
/// if vocab is contained in an accepted replacement, make ga use
/// if vocab is contained in ta choice,
/// if selected as choice, corIt
/// if written as customInput, corIt? (account for score in this)
/// for each it step
/// for each continuance
/// if not within the final message, save ignIT/incIT
/// get construct uses of type vocab for the message
List<OneConstructUse> get _vocabUses {
debugger();
final List<OneConstructUse> uses = [];
if (event.roomId == null) return uses;
List<OneConstructUse> lemmasToVocabUses(
List<Lemma> lemmas,
ConstructUseTypeEnum type,
) {
final List<OneConstructUse> uses = [];
for (final lemma in lemmas) {
if (lemma.saveVocab) {
uses.add(
OneConstructUse(
useType: type,
chatId: event.roomId!,
timeStamp: event.originServerTs,
lemma: lemma.text,
form: lemma.form,
msgId: event.eventId,
constructType: ConstructTypeEnum.vocab,
),
);
}
}
// missing vital info so return. should not happen
if (event.roomId == null) {
debugger(when: kDebugMode);
return uses;
}
List<OneConstructUse> getVocabUseForToken(PangeaToken token) {
if (originalSent?.choreo == null) {
final bool inUserL2 = originalSent?.langCode == l2Code;
return lemmasToVocabUses(
token.lemmas,
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
);
}
//
for (final step in originalSent!.choreo!.choreoSteps) {
/// if 1) accepted match 2) token is in the replacement and 3) replacement
/// is in the overall step text, then token was a ga
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted &&
(step.acceptedOrIgnoredMatch!.match.choices?.any(
(r) =>
r.value.contains(token.text.content) &&
step.text.contains(r.value),
) ??
false)) {
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.ga);
}
if (step.itStep != null) {
final bool pickedThroughIT = step.itStep!.chosenContinuance?.text
.contains(token.text.content) ??
false;
if (pickedThroughIT) {
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.corIt);
//PTODO - check if added via custom input in IT flow
}
}
}
return lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.wa);
}
/// for each token, record whether selected in ga, ta, or wa
// for each token, record whether selected in ga, ta, or wa
if (originalSent?.tokens != null) {
for (final token in originalSent!.tokens!) {
uses.addAll(getVocabUseForToken(token));
uses.addAll(_getVocabUseForToken(token));
}
}
if (originalSent?.choreo == null) return uses;
// add construct uses related to IT use
uses.addAll(_itStepsToConstructUses);
return uses;
}
/// Returns a list of [OneConstructUse] from itSteps for which the continuance
/// was selected or ignored. Correct selections are considered in the tokens
/// flow. Once all continuances have lemmas, we can do both correct and incorrect
/// in this flow. It actually doesn't do anything at all right now, because the
/// choregrapher is not returning lemmas for continuances. This is a TODO.
/// So currently only the lemmas can be gotten from the tokens for choices that
/// are actually in the final message.
List<OneConstructUse> get _itStepsToConstructUses {
final List<OneConstructUse> uses = [];
for (final itStep in originalSent!.choreo!.itSteps) {
for (final continuance in itStep.continuances) {
// this seems to always be false for continuances right now
@ -767,23 +708,98 @@ class PangeaMessageEvent {
//PTODO - account for end of flow score
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
uses.addAll(
lemmasToVocabUses(continuance.lemmas, ConstructUseTypeEnum.incIt),
_lemmasToVocabUses(
continuance.lemmas,
ConstructUseTypeEnum.incIt,
),
);
}
} else {
if (continuance.level != ChoreoConstants.levelThresholdForGreen) {
uses.addAll(
lemmasToVocabUses(continuance.lemmas, ConstructUseTypeEnum.ignIt),
_lemmasToVocabUses(
continuance.lemmas,
ConstructUseTypeEnum.ignIt,
),
);
}
}
}
}
return uses;
}
List<OneConstructUse> get grammarConstructUses {
/// Returns a list of [OneConstructUse] objects for the given [token]
/// If there is no [originalSent] or [originalSent.choreo], the [token] is
/// considered to be a [ConstructUseTypeEnum.wa] as long as it matches the target language.
/// Later on, we may want to consider putting it in some category of like 'pending'
/// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch],
/// it is considered to be a [ConstructUseTypeEnum.ga].
/// If the [token] is in the [originalSent.choreo.acceptedOrIgnoredMatch.choices],
/// it is considered to be a [ConstructUseTypeEnum.corIt].
/// If the [token] is not included in any choreoStep, it is considered to be a [ConstructUseTypeEnum.wa].
List<OneConstructUse> _getVocabUseForToken(PangeaToken token) {
debugger();
if (originalSent?.choreo == null) {
final bool inUserL2 = originalSent?.langCode == l2Code;
return _lemmasToVocabUses(
token.lemmas,
inUserL2 ? ConstructUseTypeEnum.wa : ConstructUseTypeEnum.unk,
);
}
for (final step in originalSent!.choreo!.choreoSteps) {
/// if 1) accepted match 2) token is in the replacement and 3) replacement
/// is in the overall step text, then token was a ga
if (step.acceptedOrIgnoredMatch?.status == PangeaMatchStatus.accepted &&
(step.acceptedOrIgnoredMatch!.match.choices?.any(
(r) =>
r.value.contains(token.text.content) &&
step.text.contains(r.value),
) ??
false)) {
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.ga);
}
if (step.itStep != null) {
final bool pickedThroughIT =
step.itStep!.chosenContinuance?.text.contains(token.text.content) ??
false;
if (pickedThroughIT) {
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.corIt);
//PTODO - check if added via custom input in IT flow
}
}
}
return _lemmasToVocabUses(token.lemmas, ConstructUseTypeEnum.wa);
}
/// Convert a list of [lemmas] into a list of vocab uses
/// with the given [type]
List<OneConstructUse> _lemmasToVocabUses(
List<Lemma> lemmas,
ConstructUseTypeEnum type,
) {
final List<OneConstructUse> uses = [];
for (final lemma in lemmas) {
if (lemma.saveVocab) {
uses.add(
OneConstructUse(
useType: type,
chatId: event.roomId!,
timeStamp: event.originServerTs,
lemma: lemma.text,
form: lemma.form,
msgId: event.eventId,
constructType: ConstructTypeEnum.vocab,
),
);
}
}
return uses;
}
/// get construct uses of type grammar for the message
List<OneConstructUse> get _grammarConstructUses {
final List<OneConstructUse> uses = [];
if (originalSent?.choreo == null || event.roomId == null) return uses;

View file

@ -89,7 +89,6 @@ class IGCRequestBody {
String fullText;
String userL1;
String userL2;
bool tokensOnly;
bool enableIT;
bool enableIGC;
@ -99,7 +98,6 @@ class IGCRequestBody {
required this.userL2,
required this.enableIGC,
required this.enableIT,
this.tokensOnly = false,
});
Map<String, dynamic> toJson() => {
@ -108,6 +106,5 @@ class IGCRequestBody {
ModelKey.userL2: userL2,
"enable_it": enableIT,
"enable_igc": enableIGC,
"tokens_only": tokensOnly,
};
}