2409 automatically apply accentpunctuation corrections (#2449)

* feat: automatically accept normalization error matches

* chore: on text change, if assistance state changes, update the IGC button
This commit is contained in:
ggurdin 2025-04-15 15:48:03 -04:00 committed by GitHub
parent 0dc3f13d99
commit 278219b163
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 56 additions and 15 deletions

View file

@ -59,6 +59,7 @@ class Choreographer {
final StreamController stateStream = StreamController.broadcast();
StreamSubscription? _trialStream;
StreamSubscription? _languageStream;
late AssistanceState _currentAssistanceState;
Choreographer(this.pangeaController, this.chatController) {
_initialize();
@ -86,6 +87,7 @@ class Choreographer {
// for changes like enabling autocorrect
setState();
});
_currentAssistanceState = assistanceState;
clear();
}
@ -248,6 +250,12 @@ class Choreographer {
/// Handles any changes to the text input
_onChangeListener() {
// Rebuild the IGC button if the state has changed.
// This accounts for user typing after initial IGC has completed
if (_currentAssistanceState != assistanceState) {
setState();
}
if (_noChange) {
return;
}
@ -453,6 +461,33 @@ class Choreographer {
}
}
void acceptNormalizationMatches() {
for (int i = 0; i < igc.igcTextData!.matches.length; i++) {
final isNormalizationError =
igc.spanDataController.isNormalizationError(i);
if (!isNormalizationError) continue;
final match = igc.igcTextData!.matches[i];
choreoRecord.addRecord(
_textController.text,
match: match.copyWith..status = PangeaMatchStatus.automatic,
);
igc.igcTextData!.acceptReplacement(
i,
match.match.choices!.indexWhere(
(c) => c.isBestCorrection,
),
);
_textController.setSystemText(
igc.igcTextData!.originalInput,
EditType.igc,
);
}
}
void onIgnoreMatch({required int cursorOffset}) {
try {
if (igc.igcTextData == null) {
@ -635,6 +670,7 @@ class Choreographer {
if (!stateStream.isClosed) {
stateStream.add(0);
}
_currentAssistanceState = assistanceState;
}
LayerLinkAndKey get itBarLinkAndKey =>

View file

@ -172,6 +172,7 @@ class IgcController {
}
igcTextData!.matches = confirmedMatches;
choreographer.acceptNormalizationMatches();
// TODO - for each new match,
// check if existing igcTextData has one and only one match with the same error text and correction

View file

@ -4,11 +4,10 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:matrix/matrix.dart';
import 'package:fluffychat/pangea/choreographer/models/choreo_record.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/models/span_card_model.dart';
import 'package:fluffychat/pangea/choreographer/models/span_data.dart';
import 'package:fluffychat/pangea/choreographer/repo/language_detection_repo.dart';
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
@ -363,13 +362,15 @@ class IGCTextData {
/// Returns a list of [TextSpan]s used to display the text in the input field
/// with the appropriate styling for each error match.
List<TextSpan> constructTokenSpan({
required BuildContext context,
ChoreoRecordStep? choreoStep,
TextStyle? defaultStyle,
required SpanCardModel? spanCardModel,
required bool handleClick,
required String transformTargetId,
required Room room,
}) {
final stepMatch = choreoStep?.acceptedOrIgnoredMatch;
final List<PangeaMatch> textSpanMatches = List.from(matches);
if (stepMatch != null && stepMatch.status == PangeaMatchStatus.automatic) {
textSpanMatches.add(stepMatch);
}
final List<TextSpan> items = [];
if (loading) {
@ -381,7 +382,8 @@ class IGCTextData {
];
}
final List<List<int>> matchRanges = matches
textSpanMatches.sort((a, b) => a.match.offset.compareTo(b.match.offset));
final List<List<int>> matchRanges = textSpanMatches
.map(
(match) => [
match.match.offset,
@ -403,7 +405,7 @@ class IGCTextData {
if (inMatch) {
// if the pointer is in a match, then add that match to items
// and then move the pointer to the end of the match range
final PangeaMatch match = matches[matchIndex];
final PangeaMatch match = textSpanMatches[matchIndex];
items.add(
getSpanItem(
start: match.match.offset,

View file

@ -9,7 +9,7 @@ import '../constants/match_rule_ids.dart';
import 'igc_text_data_model.dart';
import 'span_data.dart';
enum PangeaMatchStatus { open, ignored, accepted, unknown }
enum PangeaMatchStatus { open, ignored, accepted, automatic, unknown }
class PangeaMatch {
SpanData match;
@ -112,6 +112,10 @@ class PangeaMatch {
offset >= match.offset && offset < match.offset + match.length;
Color get underlineColor {
if (status == PangeaMatchStatus.automatic) {
return const Color.fromARGB(187, 132, 96, 224);
}
switch (match.rule?.id ?? "unknown") {
case MatchRuleIds.interactiveTranslation:
return const Color.fromARGB(187, 132, 96, 224);

View file

@ -175,16 +175,14 @@ class PangeaTextController extends TextEditingController {
return TextSpan(text: text, style: style);
}
final choreoSteps = choreographer.choreoRecord.choreoSteps;
return TextSpan(
style: style,
children: [
...choreographer.igc.igcTextData!.constructTokenSpan(
context: context,
choreoStep: choreoSteps.isNotEmpty ? choreoSteps.last : null,
defaultStyle: style,
spanCardModel: null,
handleClick: false,
transformTargetId: choreographer.inputTransformTargetKey,
room: choreographer.chatController.room,
),
TextSpan(text: parts[1], style: style),
],