only rebuild choreo-widgets when related data updates
This commit is contained in:
parent
ae29fbd11a
commit
4bbb81e20c
17 changed files with 288 additions and 342 deletions
|
|
@ -353,7 +353,9 @@ class ChatView extends StatelessWidget {
|
|||
child: Stack(
|
||||
children: [
|
||||
ChatEventList(controller: controller),
|
||||
ChatViewBackground(controller.choreographer),
|
||||
ChatViewBackground(
|
||||
controller.choreographer.itController.open,
|
||||
),
|
||||
],
|
||||
),
|
||||
// Pangea#
|
||||
|
|
|
|||
|
|
@ -446,7 +446,7 @@ class InputBar extends StatelessWidget {
|
|||
return;
|
||||
}
|
||||
|
||||
final match = choreographer.getMatchByOffset(
|
||||
final match = choreographer.igcController.getMatchByOffset(
|
||||
selection.baseOffset,
|
||||
);
|
||||
if (match == null) return;
|
||||
|
|
|
|||
|
|
@ -3,27 +3,24 @@ import 'package:flutter/material.dart';
|
|||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/activity_sessions/activity_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/instructions/instructions_inline_tooltip.dart';
|
||||
|
||||
class ActivityRoleTooltip extends StatelessWidget {
|
||||
final Choreographer choreographer;
|
||||
final Room room;
|
||||
final ValueNotifier<bool> hide;
|
||||
|
||||
const ActivityRoleTooltip({
|
||||
required this.choreographer,
|
||||
required this.room,
|
||||
required this.hide,
|
||||
super.key,
|
||||
});
|
||||
|
||||
Room get room => choreographer.chatController.room;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: choreographer,
|
||||
builder: (context, _) {
|
||||
if (!room.showActivityChatUI ||
|
||||
room.ownRole?.goal == null ||
|
||||
choreographer.itController.open.value) {
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: hide,
|
||||
builder: (context, hide, _) {
|
||||
if (!room.showActivityChatUI || room.ownRole?.goal == null || hide) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ class ChatInputBar extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ActivityRoleTooltip(
|
||||
choreographer: controller.choreographer,
|
||||
room: controller.room,
|
||||
hide: controller.choreographer.itController.open,
|
||||
),
|
||||
ITBar(choreographer: controller.choreographer),
|
||||
if (!controller.obscureText) ReplyDisplay(controller),
|
||||
|
|
|
|||
|
|
@ -2,18 +2,16 @@ import 'dart:ui';
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
|
||||
class ChatViewBackground extends StatelessWidget {
|
||||
final Choreographer choreographer;
|
||||
const ChatViewBackground(this.choreographer, {super.key});
|
||||
final ValueNotifier<bool> visible;
|
||||
const ChatViewBackground(this.visible, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: choreographer,
|
||||
builder: (context, _) {
|
||||
return choreographer.itController.open.value
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: visible,
|
||||
builder: (context, value, _) {
|
||||
return value
|
||||
? Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
|
|
|
|||
|
|
@ -239,7 +239,9 @@ class PangeaChatInputRow extends StatelessWidget {
|
|||
foregroundColor: theme.onBubbleColor,
|
||||
child: const Icon(Icons.mic_none_outlined),
|
||||
)
|
||||
: ChoreographerSendButton(controller: controller),
|
||||
: ChoreographerSendButton(
|
||||
choreographer: controller.choreographer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,13 +15,13 @@ import 'package:fluffychat/pangea/choreographer/enums/choreo_mode.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/pangea_match_status.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/completed_it_step.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_state.dart';
|
||||
import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/events/models/representation_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart';
|
||||
import 'package:fluffychat/pangea/events/repo/token_api_models.dart';
|
||||
import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart';
|
||||
import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart';
|
||||
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
|
||||
import 'package:fluffychat/pangea/toolbar/controllers/tts_controller.dart';
|
||||
|
|
@ -39,14 +39,14 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
late PangeaTextController textController;
|
||||
late ITController itController;
|
||||
late IgcController igc;
|
||||
late IgcController igcController;
|
||||
late ErrorService errorService;
|
||||
|
||||
ChoreoRecord? _choreoRecord;
|
||||
|
||||
final ValueNotifier<bool> _isFetching = ValueNotifier(false);
|
||||
int _timesClicked = 0;
|
||||
|
||||
int _timesClicked = 0;
|
||||
Timer? _debounceTimer;
|
||||
String? _lastChecked;
|
||||
ChoreoMode _choreoMode = ChoreoMode.igc;
|
||||
|
|
@ -65,14 +65,19 @@ class Choreographer extends ChangeNotifier {
|
|||
|
||||
void _initialize() {
|
||||
textController = PangeaTextController(choreographer: this);
|
||||
|
||||
itController = ITController(this);
|
||||
igc = IgcController(this);
|
||||
textController.addListener(_onChange);
|
||||
|
||||
errorService = ErrorService();
|
||||
errorService.addListener(notifyListeners);
|
||||
|
||||
textController.addListener(_onChange);
|
||||
itController = ITController(
|
||||
(e) => errorService.setErrorAndLock(ChoreoError(raw: e)),
|
||||
);
|
||||
itController.open.addListener(_onCloseIT);
|
||||
|
||||
igcController = IgcController(
|
||||
(e) => errorService.setErrorAndLock(ChoreoError(raw: e)),
|
||||
);
|
||||
|
||||
_languageStream =
|
||||
pangeaController.userController.languageStream.stream.listen((update) {
|
||||
|
|
@ -95,7 +100,7 @@ class Choreographer extends ChangeNotifier {
|
|||
_choreoRecord = null;
|
||||
itController.clear();
|
||||
itController.clearSourceText();
|
||||
igc.clear();
|
||||
igcController.clear();
|
||||
_resetDebounceTimer();
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +129,7 @@ class Choreographer extends ChangeNotifier {
|
|||
// if user is doing IT, call closeIT here to
|
||||
// ensure source text is replaced when needed
|
||||
if (itController.open.value && _timesClicked > 1) {
|
||||
closeIT();
|
||||
itController.closeIT();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -161,7 +166,71 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> requestLanguageAssistance() =>
|
||||
_getLanguageAssistance(manual: true);
|
||||
_startWritingAssistance(manual: true);
|
||||
|
||||
/// Handles any changes to the text input
|
||||
void _onChange() {
|
||||
if (_lastChecked != null && _lastChecked == textController.text) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastChecked = textController.text;
|
||||
if (textController.editType == EditType.it) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textController.editType == EditType.igc ||
|
||||
textController.editType == EditType.itDismissed) {
|
||||
textController.editType = EditType.keyboard;
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open IGC overlays
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
if (errorService.isError) return;
|
||||
|
||||
igcController.clear();
|
||||
if (textController.editType == EditType.keyboard) {
|
||||
itController.clearSourceText();
|
||||
}
|
||||
|
||||
_resetDebounceTimer();
|
||||
_debounceTimer ??= Timer(
|
||||
const Duration(milliseconds: ChoreoConstants.msBeforeIGCStart),
|
||||
() => _startWritingAssistance(),
|
||||
);
|
||||
|
||||
//Note: we don't set the keyboard type on each keyboard stroke so this is how we default to
|
||||
//a change being from the keyboard unless explicitly set to one of the other
|
||||
//types when that action happens (e.g. an it/igc choice is selected)
|
||||
textController.editType = EditType.keyboard;
|
||||
}
|
||||
|
||||
Future<void> _startWritingAssistance({
|
||||
bool manual = false,
|
||||
}) async {
|
||||
if (errorService.isError || isRunningIT) return;
|
||||
final SubscriptionStatus canSendStatus =
|
||||
pangeaController.subscriptionController.subscriptionStatus;
|
||||
|
||||
if (canSendStatus != SubscriptionStatus.subscribed ||
|
||||
l2Lang == null ||
|
||||
l1Lang == null ||
|
||||
(!igcEnabled && !itEnabled) ||
|
||||
(!isAutoIGCEnabled && !manual && _choreoMode != ChoreoMode.it)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_resetDebounceTimer();
|
||||
_initChoreoRecord();
|
||||
_startLoading();
|
||||
await igcController.getIGCTextData(
|
||||
textController.text,
|
||||
chatController.room.getPreviousMessages(),
|
||||
);
|
||||
_acceptNormalizationMatches();
|
||||
_stopLoading();
|
||||
}
|
||||
|
||||
Future<void> send([int recurrence = 0]) async {
|
||||
// if isFetching, already called to getLanguageHelp and hasn't completed yet
|
||||
|
|
@ -188,7 +257,7 @@ class Choreographer extends ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
if (igc.canShowFirstMatch) {
|
||||
if (igcController.canShowFirstMatch) {
|
||||
throw OpenMatchesException();
|
||||
} else if (isRunningIT) {
|
||||
// If the user is in the middle of IT, don't send the message.
|
||||
|
|
@ -213,10 +282,10 @@ class Choreographer extends ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!igc.hasIGCTextData && !itController.dismissed) {
|
||||
await _getLanguageAssistance();
|
||||
if (!igcController.hasIGCTextData && !itController.dismissed) {
|
||||
await _startWritingAssistance();
|
||||
// it's possible for this not to be true, i.e. if IGC has an error
|
||||
if (igc.hasIGCTextData) {
|
||||
if (igcController.hasIGCTextData) {
|
||||
await send(recurrence + 1);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -224,72 +293,6 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handles any changes to the text input
|
||||
void _onChange() {
|
||||
if (_lastChecked != null && _lastChecked == textController.text) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastChecked = textController.text;
|
||||
|
||||
if (textController.editType == EditType.it) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textController.editType == EditType.igc ||
|
||||
textController.editType == EditType.itDismissed) {
|
||||
textController.editType = EditType.keyboard;
|
||||
return;
|
||||
}
|
||||
|
||||
// Close any open IGC/IT overlays
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
if (errorService.isError) return;
|
||||
|
||||
igc.clear();
|
||||
if (textController.editType == EditType.keyboard) {
|
||||
itController.clearSourceText();
|
||||
}
|
||||
|
||||
_resetDebounceTimer();
|
||||
_debounceTimer ??= Timer(
|
||||
const Duration(milliseconds: ChoreoConstants.msBeforeIGCStart),
|
||||
() => _getLanguageAssistance(),
|
||||
);
|
||||
|
||||
//Note: we don't set the keyboard type on each keyboard stroke so this is how we default to
|
||||
//a change being from the keyboard unless explicitly set to one of the other
|
||||
//types when that action happens (e.g. an it/igc choice is selected)
|
||||
textController.editType = EditType.keyboard;
|
||||
}
|
||||
|
||||
/// 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> _getLanguageAssistance({
|
||||
bool manual = false,
|
||||
}) async {
|
||||
if (errorService.isError) return;
|
||||
final SubscriptionStatus canSendStatus =
|
||||
pangeaController.subscriptionController.subscriptionStatus;
|
||||
|
||||
if (canSendStatus != SubscriptionStatus.subscribed ||
|
||||
l2Lang == null ||
|
||||
l1Lang == null ||
|
||||
(!igcEnabled && !itEnabled) ||
|
||||
(!isAutoIGCEnabled && !manual && _choreoMode != ChoreoMode.it)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_resetDebounceTimer();
|
||||
_initChoreoRecord();
|
||||
|
||||
_startLoading();
|
||||
await igc.getIGCTextData();
|
||||
_stopLoading();
|
||||
}
|
||||
|
||||
Future<void> _sendWithIGC() async {
|
||||
if (chatController.sendController.text.trim().isEmpty) {
|
||||
return;
|
||||
|
|
@ -369,12 +372,14 @@ class Choreographer extends ChangeNotifier {
|
|||
if (!itMatch.updatedMatch.isITStart) {
|
||||
throw Exception("Attempted to open IT with a non-IT start match");
|
||||
}
|
||||
chatController.inputFocus.unfocus();
|
||||
|
||||
chatController.inputFocus.unfocus();
|
||||
setChoreoMode(ChoreoMode.it);
|
||||
itController.openIT(textController.text);
|
||||
|
||||
final sourceText = currentText;
|
||||
textController.setSystemText("", EditType.it);
|
||||
igc.clear();
|
||||
itController.openIT(sourceText);
|
||||
igcController.clear();
|
||||
|
||||
_initChoreoRecord();
|
||||
itMatch.setStatus(PangeaMatchStatus.accepted);
|
||||
|
|
@ -382,18 +387,26 @@ class Choreographer extends ChangeNotifier {
|
|||
"",
|
||||
match: itMatch.updatedMatch,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void closeIT() {
|
||||
itController.closeIT();
|
||||
void _onCloseIT() {
|
||||
if (itController.open.value) return;
|
||||
if (currentText.isEmpty && itController.sourceText.value != null) {
|
||||
textController.setSystemText(
|
||||
itController.sourceText.value!,
|
||||
EditType.itDismissed,
|
||||
);
|
||||
}
|
||||
|
||||
setChoreoMode(ChoreoMode.igc);
|
||||
errorService.resetError();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Continuance onSelectContinuance(int index) {
|
||||
final continuance = itController.onSelectContinuance(index);
|
||||
notifyListeners();
|
||||
return continuance;
|
||||
void onSubmitEdits(String text) {
|
||||
textController.setSystemText("", EditType.it);
|
||||
itController.onSubmitEdits(text);
|
||||
}
|
||||
|
||||
void onAcceptContinuance(int index) {
|
||||
|
|
@ -402,9 +415,6 @@ class Choreographer extends ChangeNotifier {
|
|||
textController.text + step.continuances[step.chosen].text,
|
||||
EditType.it,
|
||||
);
|
||||
textController.selection = TextSelection.collapsed(
|
||||
offset: textController.text.length,
|
||||
);
|
||||
|
||||
_initChoreoRecord();
|
||||
_choreoRecord!.addRecord(textController.text, step: step);
|
||||
|
|
@ -412,45 +422,22 @@ class Choreographer extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void setEditingSourceText(bool value) {
|
||||
itController.setEditing(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void submitSourceTextEdits(String text) {
|
||||
textController.setSystemText("", EditType.it);
|
||||
itController.onSubmitEdits();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
PangeaMatchState? getMatchByOffset(int offset) =>
|
||||
igc.getMatchByOffset(offset);
|
||||
|
||||
void clearMatches(Object error) {
|
||||
MatrixState.pAnyState.closeAllOverlays();
|
||||
igc.clearMatches();
|
||||
igcController.clearMatches();
|
||||
errorService.setError(ChoreoError(raw: error));
|
||||
}
|
||||
|
||||
Future<void> fetchSpanDetails({
|
||||
required PangeaMatchState match,
|
||||
bool force = false,
|
||||
}) =>
|
||||
igc.fetchSpanDetails(
|
||||
match: match,
|
||||
force: force,
|
||||
);
|
||||
|
||||
void onAcceptReplacement({
|
||||
required PangeaMatchState match,
|
||||
}) {
|
||||
final updatedMatch = igc.acceptReplacement(
|
||||
final updatedMatch = igcController.acceptReplacement(
|
||||
match,
|
||||
PangeaMatchStatus.accepted,
|
||||
);
|
||||
|
||||
textController.setSystemText(
|
||||
igc.currentText!,
|
||||
igcController.currentText!,
|
||||
EditType.igc,
|
||||
);
|
||||
|
||||
|
|
@ -461,19 +448,18 @@ class Choreographer extends ChangeNotifier {
|
|||
match: updatedMatch,
|
||||
);
|
||||
}
|
||||
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void onUndoReplacement(PangeaMatchState match) {
|
||||
igc.undoReplacement(match);
|
||||
igcController.undoReplacement(match);
|
||||
_choreoRecord?.choreoSteps.removeWhere(
|
||||
(step) => step.acceptedOrIgnoredMatch == match.updatedMatch,
|
||||
);
|
||||
|
||||
textController.setSystemText(
|
||||
igc.currentText!,
|
||||
igcController.currentText!,
|
||||
EditType.igc,
|
||||
);
|
||||
MatrixState.pAnyState.closeOverlay();
|
||||
|
|
@ -481,7 +467,7 @@ class Choreographer extends ChangeNotifier {
|
|||
}
|
||||
|
||||
void onIgnoreMatch({required PangeaMatchState match}) {
|
||||
final updatedMatch = igc.ignoreReplacement(match);
|
||||
final updatedMatch = igcController.ignoreReplacement(match);
|
||||
if (!updatedMatch.match.isNormalizationError()) {
|
||||
_initChoreoRecord();
|
||||
_choreoRecord!.addRecord(
|
||||
|
|
@ -493,31 +479,42 @@ class Choreographer extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void acceptNormalizationMatches() {
|
||||
final normalizationsMatches = igc.openNormalizationMatches;
|
||||
void _acceptNormalizationMatches() {
|
||||
final normalizationsMatches = igcController.openNormalizationMatches;
|
||||
if (normalizationsMatches?.isEmpty ?? true) return;
|
||||
|
||||
_initChoreoRecord();
|
||||
for (final match in normalizationsMatches!) {
|
||||
match.selectChoice(
|
||||
match.updatedMatch.match.choices!.indexWhere(
|
||||
(c) => c.isBestCorrection,
|
||||
),
|
||||
);
|
||||
try {
|
||||
for (final match in normalizationsMatches!) {
|
||||
match.selectChoice(
|
||||
match.updatedMatch.match.choices!.indexWhere(
|
||||
(c) => c.isBestCorrection,
|
||||
),
|
||||
);
|
||||
final updatedMatch = igcController.acceptReplacement(
|
||||
match,
|
||||
PangeaMatchStatus.automatic,
|
||||
);
|
||||
|
||||
final updatedMatch = igc.acceptReplacement(
|
||||
match,
|
||||
PangeaMatchStatus.automatic,
|
||||
);
|
||||
|
||||
textController.setSystemText(
|
||||
igc.currentText!,
|
||||
EditType.igc,
|
||||
);
|
||||
|
||||
_choreoRecord!.addRecord(
|
||||
currentText,
|
||||
match: updatedMatch,
|
||||
textController.setSystemText(
|
||||
igcController.currentText!,
|
||||
EditType.igc,
|
||||
);
|
||||
_choreoRecord!.addRecord(
|
||||
currentText,
|
||||
match: updatedMatch,
|
||||
);
|
||||
}
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
data: {
|
||||
"currentText": currentText,
|
||||
"l1LangCode": l1LangCode,
|
||||
"l2LangCode": l2LangCode,
|
||||
"choreoRecord": _choreoRecord?.toJson(),
|
||||
},
|
||||
);
|
||||
}
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -10,13 +10,13 @@ extension ChoregrapherUserSettingsExtension on Choreographer {
|
|||
itController.currentITStep.value?.isFinal != true;
|
||||
}
|
||||
|
||||
String? get currentIGCText => igc.currentText;
|
||||
PangeaMatchState? get openMatch => igc.openMatch;
|
||||
PangeaMatchState? get firstOpenMatch => igc.firstOpenMatch;
|
||||
List<PangeaMatchState>? get openIGCMatches => igc.openMatches;
|
||||
List<PangeaMatchState>? get closedIGCMatches => igc.closedMatches;
|
||||
bool get canShowFirstIGCMatch => igc.canShowFirstMatch;
|
||||
bool get hasIGCTextData => igc.hasIGCTextData;
|
||||
String? get currentIGCText => igcController.currentText;
|
||||
PangeaMatchState? get openMatch => igcController.openMatch;
|
||||
PangeaMatchState? get firstOpenMatch => igcController.firstOpenMatch;
|
||||
List<PangeaMatchState>? get openIGCMatches => igcController.openMatches;
|
||||
List<PangeaMatchState>? get closedIGCMatches => igcController.closedMatches;
|
||||
bool get canShowFirstIGCMatch => igcController.canShowFirstMatch;
|
||||
bool get hasIGCTextData => igcController.hasIGCTextData;
|
||||
|
||||
AssistanceState get assistanceState {
|
||||
final isSubscribed = pangeaController.subscriptionController.isSubscribed;
|
||||
|
|
@ -29,12 +29,12 @@ extension ChoregrapherUserSettingsExtension on Choreographer {
|
|||
return AssistanceState.error;
|
||||
}
|
||||
|
||||
if (igc.hasOpenMatches || isRunningIT) {
|
||||
if (igcController.hasOpenMatches || isRunningIT) {
|
||||
return AssistanceState.fetched;
|
||||
}
|
||||
|
||||
if (isFetching.value) return AssistanceState.fetching;
|
||||
if (!igc.hasIGCTextData) return AssistanceState.notFetched;
|
||||
if (!igcController.hasIGCTextData) return AssistanceState.notFetched;
|
||||
return AssistanceState.complete;
|
||||
}
|
||||
|
||||
|
|
@ -57,13 +57,13 @@ extension ChoregrapherUserSettingsExtension on Choreographer {
|
|||
if (isFetching.value) return false;
|
||||
|
||||
// they're supposed to run IGC but haven't yet, don't let them send
|
||||
if (!igc.hasIGCTextData) {
|
||||
if (!igcController.hasIGCTextData) {
|
||||
return itController.dismissed;
|
||||
}
|
||||
|
||||
// if they have relevant matches, don't let them send
|
||||
final hasITMatches = igc.hasOpenITMatches;
|
||||
final hasIGCMatches = igc.hasOpenIGCMatches;
|
||||
final hasITMatches = igcController.hasOpenITMatches;
|
||||
final hasIGCMatches = igcController.hasOpenIGCMatches;
|
||||
if ((itEnabled && hasITMatches) || (igcEnabled && hasIGCMatches)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,7 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:matrix/matrix.dart' hide Result;
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/error_service.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/controllers/extensions/choregrapher_user_settings_extension.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/choreo_mode.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/enums/pangea_match_status.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/igc_text_data_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/pangea_match_model.dart';
|
||||
|
|
@ -18,16 +10,16 @@ import 'package:fluffychat/pangea/choreographer/repo/igc_repo.dart';
|
|||
import 'package:fluffychat/pangea/choreographer/repo/igc_request_model.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/span_data_repo.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/span_data_request.dart';
|
||||
import 'package:fluffychat/pangea/events/event_wrappers/pangea_message_event.dart';
|
||||
import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
||||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../../common/utils/error_handler.dart';
|
||||
|
||||
class IgcController {
|
||||
final Choreographer _choreographer;
|
||||
final Function(Object) onError;
|
||||
|
||||
bool _isFetching = false;
|
||||
IGCTextData? _igcTextData;
|
||||
|
||||
IgcController(this._choreographer);
|
||||
IgcController(this.onError);
|
||||
|
||||
String? get currentText => _igcTextData?.currentText;
|
||||
bool get hasOpenMatches => _igcTextData?.hasOpenMatches == true;
|
||||
|
|
@ -42,12 +34,10 @@ class IgcController {
|
|||
_igcTextData?.openNormalizationMatches;
|
||||
|
||||
bool get canShowFirstMatch => _igcTextData?.firstOpenMatch != null;
|
||||
bool get hasIGCTextData {
|
||||
if (_igcTextData == null) return false;
|
||||
return _igcTextData!.currentText == _choreographer.currentText;
|
||||
}
|
||||
bool get hasIGCTextData => _igcTextData != null;
|
||||
|
||||
void clear() {
|
||||
_isFetching = false;
|
||||
_igcTextData = null;
|
||||
MatrixState.pAnyState.closeAllOverlays();
|
||||
}
|
||||
|
|
@ -64,7 +54,8 @@ class IgcController {
|
|||
if (_igcTextData == null) {
|
||||
throw "acceptReplacement called with null igcTextData";
|
||||
}
|
||||
return _igcTextData!.acceptReplacement(match, status);
|
||||
final updateMatch = _igcTextData!.acceptReplacement(match, status);
|
||||
return updateMatch;
|
||||
}
|
||||
|
||||
PangeaMatch ignoreReplacement(PangeaMatchState match) {
|
||||
|
|
@ -82,24 +73,25 @@ class IgcController {
|
|||
_igcTextData!.undoReplacement(match);
|
||||
}
|
||||
|
||||
Future<void> getIGCTextData() async {
|
||||
if (_choreographer.currentText.isEmpty) return clear();
|
||||
debugPrint('getIGCTextData called with ${_choreographer.currentText}');
|
||||
|
||||
Future<void> getIGCTextData(
|
||||
String text,
|
||||
List<PreviousMessage> prevMessages,
|
||||
) async {
|
||||
if (text.isEmpty) return clear();
|
||||
if (_isFetching) return;
|
||||
_isFetching = true;
|
||||
final IGCRequestModel reqBody = IGCRequestModel(
|
||||
fullText: _choreographer.currentText,
|
||||
userId: _choreographer.pangeaController.userController.userId!,
|
||||
userL1: _choreographer.l1LangCode!,
|
||||
userL2: _choreographer.l2LangCode!,
|
||||
enableIGC: _choreographer.igcEnabled &&
|
||||
_choreographer.choreoMode != ChoreoMode.it,
|
||||
enableIT: _choreographer.itEnabled &&
|
||||
_choreographer.choreoMode != ChoreoMode.it,
|
||||
prevMessages: _prevMessages(),
|
||||
fullText: text,
|
||||
userId: MatrixState.pangeaController.userController.userId!,
|
||||
userL1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
userL2: MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
enableIGC: true,
|
||||
enableIT: true,
|
||||
prevMessages: prevMessages,
|
||||
);
|
||||
|
||||
final res = await IgcRepo.get(
|
||||
_choreographer.pangeaController.userController.accessToken,
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
reqBody,
|
||||
).timeout(
|
||||
(const Duration(seconds: 10)),
|
||||
|
|
@ -111,37 +103,18 @@ class IgcController {
|
|||
);
|
||||
|
||||
if (res.isError) {
|
||||
_choreographer.errorService.setErrorAndLock(
|
||||
ChoreoError(raw: res.asError),
|
||||
);
|
||||
onError(res.asError!);
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// this will happen when the user changes the input while igc is fetching results
|
||||
if (res.result!.originalInput.trim() != _choreographer.currentText.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_isFetching) return;
|
||||
final response = res.result!;
|
||||
_igcTextData = IGCTextData(
|
||||
originalInput: response.originalInput,
|
||||
matches: response.matches,
|
||||
);
|
||||
|
||||
try {
|
||||
_choreographer.acceptNormalizationMatches();
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
s: s,
|
||||
level: SentryLevel.warning,
|
||||
data: {
|
||||
"igcResponse": response.toJson(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_isFetching = false;
|
||||
if (_igcTextData != null) {
|
||||
for (final match in _igcTextData!.openMatches) {
|
||||
fetchSpanDetails(match: match).catchError((e) {});
|
||||
|
|
@ -159,12 +132,12 @@ class IgcController {
|
|||
}
|
||||
|
||||
final response = await SpanDataRepo.get(
|
||||
_choreographer.pangeaController.userController.accessToken,
|
||||
MatrixState.pangeaController.userController.accessToken,
|
||||
request: SpanDetailsRequest(
|
||||
userL1: _choreographer.l1LangCode!,
|
||||
userL2: _choreographer.l2LangCode!,
|
||||
enableIGC: _choreographer.igcEnabled,
|
||||
enableIT: _choreographer.itEnabled,
|
||||
userL1: MatrixState.pangeaController.languageController.activeL1Code()!,
|
||||
userL2: MatrixState.pangeaController.languageController.activeL2Code()!,
|
||||
enableIGC: true,
|
||||
enableIT: true,
|
||||
span: span,
|
||||
),
|
||||
).timeout(
|
||||
|
|
@ -182,39 +155,4 @@ class IgcController {
|
|||
|
||||
_igcTextData?.setSpanData(match, response.result!.span);
|
||||
}
|
||||
|
||||
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()?.transcript.text.trim(); // trim whitespace
|
||||
if (content == null) continue;
|
||||
messages.add(
|
||||
PreviousMessage(
|
||||
content: content,
|
||||
sender: event.senderId,
|
||||
timestamp: event.originServerTs,
|
||||
),
|
||||
);
|
||||
if (messages.length >= numMessages) {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,6 @@ import 'package:flutter/foundation.dart';
|
|||
|
||||
import 'package:async/async.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/models/gold_route_tracker.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/models/it_step.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/it_repo.dart';
|
||||
|
|
@ -16,21 +13,20 @@ import 'package:fluffychat/widgets/future_loading_dialog.dart';
|
|||
import 'package:fluffychat/widgets/matrix.dart';
|
||||
import '../models/completed_it_step.dart';
|
||||
import '../repo/it_request_model.dart';
|
||||
import 'choreographer.dart';
|
||||
|
||||
class ITController {
|
||||
final Choreographer _choreographer;
|
||||
final ValueNotifier<String?> _sourceText = ValueNotifier(null);
|
||||
final Function(Object) onError;
|
||||
|
||||
final ValueNotifier<ITStep?> _currentITStep = ValueNotifier(null);
|
||||
final Queue<Completer<ITStep>> _queue = Queue();
|
||||
GoldRouteTracker? _goldRouteTracker;
|
||||
|
||||
final ValueNotifier<String?> _sourceText = ValueNotifier(null);
|
||||
final ValueNotifier<ITStep?> _currentITStep = ValueNotifier(null);
|
||||
final ValueNotifier<bool> _open = ValueNotifier(false);
|
||||
final ValueNotifier<bool> _editing = ValueNotifier(false);
|
||||
bool _dismissed = false;
|
||||
|
||||
ITController(this._choreographer);
|
||||
ITController(this.onError);
|
||||
|
||||
ValueNotifier<bool> get open => _open;
|
||||
ValueNotifier<bool> get editing => _editing;
|
||||
|
|
@ -47,8 +43,6 @@ class ITController {
|
|||
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,
|
||||
);
|
||||
|
|
@ -72,8 +66,6 @@ class ITController {
|
|||
_queue.clear();
|
||||
_currentITStep.value = null;
|
||||
_goldRouteTracker = null;
|
||||
|
||||
_choreographer.setChoreoMode(ChoreoMode.igc);
|
||||
}
|
||||
|
||||
void clearSourceText() {
|
||||
|
|
@ -81,38 +73,30 @@ class ITController {
|
|||
}
|
||||
|
||||
void dispose() {
|
||||
_open.dispose();
|
||||
_currentITStep.dispose();
|
||||
_editing.dispose();
|
||||
_sourceText.dispose();
|
||||
}
|
||||
|
||||
void openIT(String sourceText) {
|
||||
_sourceText.value = sourceText;
|
||||
void openIT(String text) {
|
||||
_sourceText.value = text;
|
||||
_open.value = true;
|
||||
continueIT();
|
||||
}
|
||||
|
||||
void closeIT() {
|
||||
// if the user hasn't gone through any IT steps, reset the text
|
||||
if (_choreographer.currentText.isEmpty && _sourceText.value != null) {
|
||||
_choreographer.textController.setSystemText(
|
||||
_sourceText.value!,
|
||||
EditType.itDismissed,
|
||||
);
|
||||
}
|
||||
|
||||
clear(dismissed: true);
|
||||
}
|
||||
void closeIT() => clear(dismissed: true);
|
||||
|
||||
void setEditing(bool value) {
|
||||
_editing.value = value;
|
||||
}
|
||||
|
||||
void onSubmitEdits() {
|
||||
void onSubmitEdits(String text) {
|
||||
_editing.value = false;
|
||||
_queue.clear();
|
||||
_currentITStep.value = null;
|
||||
_goldRouteTracker = null;
|
||||
_sourceText.value = text;
|
||||
continueIT();
|
||||
}
|
||||
|
||||
|
|
@ -163,13 +147,13 @@ class ITController {
|
|||
if (_currentITStep.value == null) {
|
||||
await _initTranslationData();
|
||||
} else if (_queue.isEmpty) {
|
||||
_choreographer.closeIT();
|
||||
closeIT();
|
||||
} else {
|
||||
final nextStepCompleter = _queue.removeFirst();
|
||||
_currentITStep.value = await nextStepCompleter.future;
|
||||
}
|
||||
} catch (e) {
|
||||
_choreographer.errorService.setErrorAndLock(ChoreoError(raw: e));
|
||||
onError(e);
|
||||
} finally {
|
||||
_continuing = false;
|
||||
}
|
||||
|
|
@ -179,9 +163,7 @@ class ITController {
|
|||
final res = await _safeRequest("");
|
||||
if (_sourceText.value == null || !_open.value) return;
|
||||
if (res.isError || res.result?.goldContinuances == null) {
|
||||
_choreographer.errorService.setErrorAndLock(
|
||||
ChoreoError(raw: res.asError),
|
||||
);
|
||||
onError(res.asError!);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ class ITRequestModel {
|
|||
final String customInput;
|
||||
final String sourceLangCode;
|
||||
final String targetLangCode;
|
||||
final String userId;
|
||||
final String roomId;
|
||||
|
||||
final String? goldTranslation;
|
||||
final List<Continuance>? goldContinuances;
|
||||
|
|
@ -19,8 +17,6 @@ class ITRequestModel {
|
|||
required this.customInput,
|
||||
required this.sourceLangCode,
|
||||
required this.targetLangCode,
|
||||
required this.userId,
|
||||
required this.roomId,
|
||||
required this.goldTranslation,
|
||||
required this.goldContinuances,
|
||||
});
|
||||
|
|
@ -30,8 +26,6 @@ class ITRequestModel {
|
|||
customInput: json['custom_input'],
|
||||
sourceLangCode: json[ModelKey.srcLang],
|
||||
targetLangCode: json[ModelKey.tgtLang],
|
||||
userId: json['user_id'],
|
||||
roomId: json['room_id'],
|
||||
goldTranslation: json['gold_translation'],
|
||||
goldContinuances: json['gold_continuances'] != null
|
||||
? (json['gold_continuances'])
|
||||
|
|
@ -45,8 +39,6 @@ class ITRequestModel {
|
|||
'custom_input': customInput,
|
||||
ModelKey.srcLang: sourceLangCode,
|
||||
ModelKey.tgtLang: targetLangCode,
|
||||
'user_id': userId,
|
||||
'room_id': roomId,
|
||||
'gold_translation': goldTranslation,
|
||||
'gold_continuances': goldContinuances != null
|
||||
? List.from(goldContinuances!.map((e) => e.toJson()))
|
||||
|
|
@ -62,8 +54,6 @@ class ITRequestModel {
|
|||
other.customInput == customInput &&
|
||||
other.sourceLangCode == sourceLangCode &&
|
||||
other.targetLangCode == targetLangCode &&
|
||||
other.userId == userId &&
|
||||
other.roomId == roomId &&
|
||||
other.goldTranslation == goldTranslation &&
|
||||
listEquals(other.goldContinuances, goldContinuances);
|
||||
}
|
||||
|
|
@ -74,8 +64,6 @@ class ITRequestModel {
|
|||
customInput.hashCode ^
|
||||
sourceLangCode.hashCode ^
|
||||
targetLangCode.hashCode ^
|
||||
userId.hashCode ^
|
||||
roomId.hashCode ^
|
||||
goldTranslation.hashCode ^
|
||||
Object.hashAll(goldContinuances ?? []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
|
||||
try {
|
||||
setState(() => _loadingChoices = true);
|
||||
await widget.choreographer.fetchSpanDetails(
|
||||
await widget.choreographer.igcController.fetchSpanDetails(
|
||||
match: widget.match,
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
@ -83,7 +83,9 @@ class SpanCardState extends State<SpanCard> {
|
|||
'No choices available for span ${widget.match.updatedMatch.match.message}',
|
||||
);
|
||||
}
|
||||
setState(() => _loadingChoices = false);
|
||||
if (mounted) {
|
||||
setState(() => _loadingChoices = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
|
||||
try {
|
||||
_feedbackModel.setState(FeedbackLoading<String>());
|
||||
await widget.choreographer.fetchSpanDetails(
|
||||
await widget.choreographer.igcController.fetchSpanDetails(
|
||||
match: widget.match,
|
||||
force: true,
|
||||
);
|
||||
|
|
@ -157,7 +159,7 @@ class SpanCardState extends State<SpanCard> {
|
|||
void _showFirstMatch() {
|
||||
if (widget.choreographer.canShowFirstIGCMatch) {
|
||||
OverlayUtil.showIGCMatch(
|
||||
widget.choreographer.igc.firstOpenMatch!,
|
||||
widget.choreographer.igcController.firstOpenMatch!,
|
||||
widget.choreographer,
|
||||
context,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
MatrixState.pAnyState.closeOverlay("it_feedback_card");
|
||||
Continuance continuance;
|
||||
try {
|
||||
continuance = widget.choreographer.onSelectContinuance(index);
|
||||
continuance =
|
||||
widget.choreographer.itController.onSelectContinuance(index);
|
||||
} catch (e, s) {
|
||||
ErrorHandler.logError(
|
||||
e: e,
|
||||
|
|
@ -143,7 +144,7 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
"index": index,
|
||||
},
|
||||
);
|
||||
widget.choreographer.closeIT();
|
||||
widget.choreographer.itController.closeIT();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -174,7 +175,7 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
"index": index,
|
||||
},
|
||||
);
|
||||
widget.choreographer.closeIT();
|
||||
widget.choreographer.itController.closeIT();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -217,12 +218,14 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
spacing: 12.0,
|
||||
children: [
|
||||
_ITBarHeader(
|
||||
onClose: widget.choreographer.closeIT,
|
||||
onClose: widget.choreographer.itController.closeIT,
|
||||
setEditing: widget.choreographer.itController.setEditing,
|
||||
editing: widget.choreographer.itController.editing,
|
||||
sourceTextController: _sourceTextController,
|
||||
sourceText: _sourceText,
|
||||
onSubmitEdits: widget.choreographer.submitSourceTextEdits,
|
||||
onSubmitEdits: (_) => widget.choreographer.onSubmitEdits(
|
||||
_sourceTextController.text,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
|
|
@ -241,7 +244,8 @@ class ITBarState extends State<ITBar> with SingleTickerProviderStateMixin {
|
|||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: widget.choreographer.closeIT,
|
||||
onPressed:
|
||||
widget.choreographer.itController.closeIT,
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
size: 20,
|
||||
|
|
|
|||
|
|
@ -7,33 +7,31 @@ import 'package:fluffychat/pangea/choreographer/controllers/extensions/choreogra
|
|||
import 'package:fluffychat/pangea/choreographer/enums/assistance_state_enum.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/overlay.dart';
|
||||
import '../../../pages/chat/chat.dart';
|
||||
|
||||
class ChoreographerSendButton extends StatelessWidget {
|
||||
final Choreographer choreographer;
|
||||
const ChoreographerSendButton({
|
||||
super.key,
|
||||
required this.controller,
|
||||
required this.choreographer,
|
||||
});
|
||||
final ChatController controller;
|
||||
|
||||
Future<void> _onPressed(BuildContext context) async {
|
||||
controller.choreographer.onClickSend();
|
||||
choreographer.onClickSend();
|
||||
try {
|
||||
await controller.choreographer.send();
|
||||
await choreographer.send();
|
||||
} on ShowPaywallException {
|
||||
PaywallCard.show(
|
||||
context,
|
||||
controller.choreographer.inputTransformTargetKey,
|
||||
choreographer.inputTransformTargetKey,
|
||||
);
|
||||
} on OpenMatchesException {
|
||||
if (controller.choreographer.firstOpenMatch != null) {
|
||||
if (controller.choreographer.firstOpenMatch!.updatedMatch.isITStart) {
|
||||
controller.choreographer
|
||||
.openIT(controller.choreographer.firstOpenMatch!);
|
||||
if (choreographer.firstOpenMatch != null) {
|
||||
if (choreographer.firstOpenMatch!.updatedMatch.isITStart) {
|
||||
choreographer.openIT(choreographer.firstOpenMatch!);
|
||||
} else {
|
||||
OverlayUtil.showIGCMatch(
|
||||
controller.choreographer.firstOpenMatch!,
|
||||
controller.choreographer,
|
||||
choreographer.firstOpenMatch!,
|
||||
choreographer,
|
||||
context,
|
||||
);
|
||||
}
|
||||
|
|
@ -45,8 +43,8 @@ class ChoreographerSendButton extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: Listenable.merge([
|
||||
controller.choreographer.textController,
|
||||
controller.choreographer.isFetching,
|
||||
choreographer.textController,
|
||||
choreographer.isFetching,
|
||||
]),
|
||||
builder: (context, _) {
|
||||
return Container(
|
||||
|
|
@ -54,9 +52,8 @@ class ChoreographerSendButton extends StatelessWidget {
|
|||
alignment: Alignment.center,
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.send_outlined),
|
||||
color: controller.choreographer.assistanceState
|
||||
.sendButtonColor(context),
|
||||
onPressed: controller.choreographer.isFetching.value
|
||||
color: choreographer.assistanceState.sendButtonColor(context),
|
||||
onPressed: choreographer.isFetching.value
|
||||
? null
|
||||
: () => _onPressed(context),
|
||||
tooltip: L10n.of(context).send,
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ class StartIGCButtonState extends State<StartIGCButton>
|
|||
|
||||
void _showFirstMatch() {
|
||||
if (widget.controller.choreographer.canShowFirstIGCMatch) {
|
||||
final match = widget.controller.choreographer.igc.firstOpenMatch;
|
||||
final match =
|
||||
widget.controller.choreographer.igcController.firstOpenMatch;
|
||||
if (match == null) return;
|
||||
if (match.updatedMatch.isITStart) {
|
||||
widget.controller.choreographer.openIT(match);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import 'package:fluffychat/pangea/bot/utils/bot_room_extension.dart';
|
|||
import 'package:fluffychat/pangea/chat/constants/default_power_level.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart';
|
||||
import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart';
|
||||
import 'package:fluffychat/pangea/choreographer/repo/igc_request_model.dart';
|
||||
import 'package:fluffychat/pangea/common/constants/model_keys.dart';
|
||||
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
|
||||
import 'package:fluffychat/pangea/constructs/construct_identifier.dart';
|
||||
|
|
|
|||
|
|
@ -385,4 +385,40 @@ extension EventsRoomExtension on Room {
|
|||
|
||||
return allPangeaMessages;
|
||||
}
|
||||
|
||||
List<PreviousMessage> getPreviousMessages({int numMessages = 5}) {
|
||||
if (timeline == null) return [];
|
||||
final events = timeline!.events
|
||||
.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: timeline!,
|
||||
ownMessage: event.senderId == client.userID,
|
||||
).getSpeechToTextLocal()?.transcript.text.trim();
|
||||
|
||||
if (content == null) continue;
|
||||
messages.add(
|
||||
PreviousMessage(
|
||||
content: content,
|
||||
sender: event.senderId,
|
||||
timestamp: event.originServerTs,
|
||||
),
|
||||
);
|
||||
if (messages.length >= numMessages) {
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue