feat: tweaking morphogical edit flow and changing lemma meaning to edit flow

This commit is contained in:
wcjord 2025-01-10 19:28:16 -05:00
parent 47930eea1b
commit 7054d92532
8 changed files with 169 additions and 72 deletions

View file

@ -4705,5 +4705,6 @@
"lemmasNeverUsedCorrectly": "Number of lemmas used correctly 0 times",
"available": "Available",
"unavailable": "Unavailable",
"accessingMemberAnalytics": "Accessing member analytics..."
}
"accessingMemberAnalytics": "Accessing member analytics...",
"editLemmaMeaning": "Pangea Bot makes mistakes too! What should be the definition of this lemma?"
}

View file

@ -81,6 +81,15 @@ class ModelKey {
static const String tokensWritten = "tokens_written";
static const String choreoRecord = "choreo_record";
/// This is strictly for use in message content jsons
/// in order to flag that the message edit was done in order
/// to edit some message data such as tokens, morph tags, etc.
/// This will help us know to omit the message from notifications,
/// bot responses, etc. It will also help use find the message if
/// we want to gather user edits for LLM fine-tuning.
static const String messageTags = "p.tag";
static const String messageTagMorphEdit = "morph_edit";
static const String baseDefinition = "base_definition";
static const String targetDefinition = "target_definition";
static const String basePartOfSpeech = "base_part_of_speech";

View file

@ -4,18 +4,8 @@ import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:collection/collection.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/markdown.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:fluffychat/pangea/constants/bot_mode.dart';
import 'package:fluffychat/pangea/constants/class_code_constants.dart';
import 'package:fluffychat/pangea/constants/class_default_values.dart';
@ -31,6 +21,15 @@ import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:html_unescape/html_unescape.dart';
import 'package:matrix/matrix.dart' as matrix;
import 'package:matrix/matrix.dart';
import 'package:matrix/src/utils/markdown.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import '../../../config/app_config.dart';
import '../../constants/pangea_event_types.dart';
import '../../models/choreo_record.dart';
@ -178,6 +177,7 @@ extension PangeaRoom on Room {
PangeaMessageTokens? tokensSent,
PangeaMessageTokens? tokensWritten,
ChoreoRecord? choreo,
String? messageTag,
}) =>
_pangeaSendTextEvent(
message,
@ -194,6 +194,7 @@ extension PangeaRoom on Room {
tokensSent: tokensSent,
tokensWritten: tokensWritten,
choreo: choreo,
messageTag: messageTag,
);
Future<String> updateStateEvent(Event stateEvent) =>

View file

@ -236,6 +236,7 @@ extension EventsRoomExtension on Room {
PangeaMessageTokens? tokensSent,
PangeaMessageTokens? tokensWritten,
ChoreoRecord? choreo,
String? messageTag,
}) {
// if (parseCommands) {
// return client.parseAndRunCommand(this, message,
@ -248,12 +249,26 @@ extension EventsRoomExtension on Room {
final event = <String, dynamic>{
'msgtype': msgtype,
'body': message,
ModelKey.choreoRecord: choreo?.toJson(),
ModelKey.originalSent: originalSent?.toJson(),
ModelKey.originalWritten: originalWritten?.toJson(),
ModelKey.tokensSent: tokensSent?.toJson(),
ModelKey.tokensWritten: tokensWritten?.toJson(),
};
if (choreo != null) {
event[ModelKey.choreoRecord] = choreo.toJson();
}
if (originalSent != null) {
event[ModelKey.originalSent] = originalSent.toJson();
}
if (originalWritten != null) {
event[ModelKey.originalWritten] = originalWritten.toJson();
}
if (tokensSent != null) {
event[ModelKey.tokensSent] = tokensSent.toJson();
}
if (tokensWritten != null) {
event[ModelKey.tokensWritten] = tokensWritten.toJson();
}
if (messageTag != null) {
event[ModelKey.messageTags] = messageTag;
}
if (parseMarkdown) {
final html = markdown(
event['body'],

View file

@ -1,16 +1,15 @@
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:fluffychat/pangea/models/content_feedback.dart';
import 'package:fluffychat/pangea/network/urls.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_response.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import '../../config/environment.dart';
import '../../network/requests.dart';
@ -19,7 +18,14 @@ class LemmaInfoRepo {
static final Map<LemmaInfoRequest, LemmaInfoResponse> _cache = {};
static final Map<LemmaInfoRequest, DateTime> _cacheTimestamps = {};
static const Duration _cacheDuration = Duration(days: 2);
static const Duration _cacheDuration = Duration(days: 30);
static void set(LemmaInfoRequest request, LemmaInfoResponse response) {
_cache[request] = response;
// set it to sometime in the future so we keep it in the cache for a while
_cacheTimestamps[request] = DateTime.now().add(const Duration(days: 365));
}
static Future<LemmaInfoResponse> get(
LemmaInfoRequest request, [

View file

@ -1,14 +1,15 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/pangea/choreographer/widgets/text_loading_shimmer.dart';
import 'package:fluffychat/pangea/constants/language_constants.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_repo.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_request.dart';
import 'package:fluffychat/utils/feedback_dialog.dart';
import 'package:fluffychat/pangea/repo/lemma_info/lemma_info_response.dart';
import 'package:fluffychat/widgets/matrix.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class LemmaMeaningWidget extends StatefulWidget {
final String lemma;
@ -27,56 +28,57 @@ class LemmaMeaningWidget extends StatefulWidget {
}
class LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
late Future<String> _definitionFuture;
bool _editMode = false;
late TextEditingController _controller;
@override
void initState() {
super.initState();
_definitionFuture = _fetchDefinition();
_controller = TextEditingController();
}
Future<String> _fetchDefinition([String? feedback]) async {
final LemmaInfoRequest lemmaDefReq = LemmaInfoRequest(
lemma: widget.lemma,
partOfSpeech: widget.pos,
/// This assumes that the user's L2 is the language of the lemma
lemmaLang: widget.langCode,
userL1:
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
);
final res = await LemmaInfoRepo.get(lemmaDefReq, feedback);
return res.meaning;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _showFeedbackDialog(String offendingContentString) async {
// setState(() {
// _definitionFuture = _fetchDefinition(offendingContentString);
// });
await showFeedbackDialog(
context,
Text(offendingContentString),
(feedback) async {
setState(() {
_definitionFuture = _fetchDefinition(feedback);
});
return;
},
LemmaInfoRequest get _request => LemmaInfoRequest(
lemma: widget.lemma,
partOfSpeech: widget.pos,
/// This assumes that the user's L2 is the language of the lemma
lemmaLang: widget.langCode,
userL1:
MatrixState.pangeaController.languageController.userL1?.langCode ??
LanguageKeys.defaultLanguage,
);
Future<LemmaInfoResponse> _lemmaMeaning() => LemmaInfoRepo.get(_request);
void _toggleEditMode(bool value) => setState(() => _editMode = value);
Future<void> editLemmaMeaning(String userEdit) async {
final originalMeaning = await _lemmaMeaning();
LemmaInfoRepo.set(
_request,
LemmaInfoResponse(emoji: originalMeaning.emoji, meaning: userEdit),
);
_toggleEditMode(false);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _definitionFuture,
return FutureBuilder<LemmaInfoResponse>(
future: _lemmaMeaning(),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const TextLoadingShimmer();
}
if (snapshot.hasError) {
if (snapshot.hasError || snapshot.data == null) {
debugger(when: kDebugMode);
return Text(
snapshot.error.toString(),
@ -84,11 +86,60 @@ class LemmaMeaningWidgetState extends State<LemmaMeaningWidget> {
);
}
if (_editMode) {
_controller.text = snapshot.data!.meaning;
return Container(
constraints: const BoxConstraints(
maxWidth: AppConfig.toolbarMinWidth,
maxHeight: 160,
),
child: Column(
children: [
Text(
L10n.of(context).editLemmaMeaning,
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
const SizedBox(height: 10),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: TextField(
controller: _controller,
onSubmitted: editLemmaMeaning,
decoration: InputDecoration(
hintText: snapshot.data!.meaning,
),
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () => _toggleEditMode(false),
child: Text(L10n.of(context).cancel),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () =>
_controller.text != snapshot.data!.meaning &&
_controller.text.isNotEmpty
? editLemmaMeaning(_controller.text)
: null,
child: Text(L10n.of(context).saveChanges),
),
],
),
],
),
);
}
return GestureDetector(
onLongPress: () => _showFeedbackDialog(snapshot.data as String),
onDoubleTap: () => _showFeedbackDialog(snapshot.data as String),
onLongPress: () => _toggleEditMode(true),
onDoubleTap: () => _toggleEditMode(true),
child: Text(
snapshot.data as String,
snapshot.data!.meaning,
textAlign: TextAlign.center,
),
);

View file

@ -1,29 +1,30 @@
// stateful widget that displays morphological label and a shimmer effect while the text is loading
// takes a token and morphological feature as input
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:fluffychat/pangea/constants/model_keys.dart';
import 'package:fluffychat/pangea/constants/morph_categories_and_labels.dart';
import 'package:fluffychat/pangea/extensions/pangea_room_extension/pangea_room_extension.dart';
import 'package:fluffychat/pangea/matrix_event_wrappers/pangea_message_event.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/models/tokens_event_content_model.dart';
import 'package:fluffychat/pangea/utils/bot_style.dart';
import 'package:fluffychat/pangea/utils/error_handler.dart';
import 'package:fluffychat/pangea/utils/grammar/get_grammar_copy.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
class MorphologicalCenterWidget extends StatefulWidget {
final PangeaToken token;
final String morphFeature;
final PangeaMessageEvent pangeaMessageEvent;
final MessageOverlayController overlayController;
const MorphologicalCenterWidget({
required this.token,
required this.morphFeature,
required this.pangeaMessageEvent,
required this.overlayController,
super.key,
});
@ -88,6 +89,8 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
// send a new message as an edit to original message to the server
// including the new tokens
// marking the message as a morphological edit will allow use to filter
// from some processing and potentially find the data for LLM fine-tuning
await pm.room.pangeaSendTextEvent(
pm.messageDisplayText,
editEventId: pm.eventId,
@ -98,7 +101,12 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
? PangeaMessageTokens(tokens: pm.originalWritten!.tokens!)
: null,
choreo: pm.originalSent?.choreo,
messageTag: ModelKey.messageTagMorphEdit,
);
setState(() {
editMode = false;
});
} catch (e) {
SnackBar(
content: Text(L10n.of(context).oopsSomethingWentWrong),
@ -117,8 +125,10 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
List<String> get allMorphTagsForEdit {
final List<String> tags = getLabelsForMorphCategory(widget.morphFeature)
.where((tag) => !["punct", "space", "sym", "x", "other"]
.contains(tag.toLowerCase()))
.where(
(tag) => !["punct", "space", "sym", "x", "other"]
.contains(tag.toLowerCase()),
)
.toList();
// as long as the feature is not POS, add a nan tag
@ -153,6 +163,7 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
Text(
L10n.of(context).editMorphologicalLabel,
textAlign: TextAlign.center,
style: const TextStyle(fontStyle: FontStyle.italic),
),
const SizedBox(height: 10),
Container(
@ -206,7 +217,6 @@ class MorphologicalCenterWidgetState extends State<MorphologicalCenterWidget> {
context: context,
) ??
tag,
style: BotStyle.text(context),
textAlign: TextAlign.center,
),
),

View file

@ -1,15 +1,15 @@
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:fluffychat/pangea/controllers/message_analytics_controller.dart';
import 'package:fluffychat/pangea/models/pangea_token_model.dart';
import 'package:fluffychat/pangea/widgets/chat/message_selection_overlay.dart';
import 'package:fluffychat/pangea/widgets/chat/toolbar_content_loading_indicator.dart';
import 'package:fluffychat/pangea/widgets/practice_activity/practice_activity_card.dart';
import 'package:fluffychat/pangea/widgets/word_zoom/lemma_meaning_widget.dart';
import 'package:fluffychat/pangea/widgets/word_zoom/morphs/morphological_center_widget.dart';
import 'package:fluffychat/pangea/widgets/word_zoom/word_zoom_widget.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class WordZoomCenterWidget extends StatelessWidget {
final WordZoomSelection? selectionType;
@ -27,6 +27,9 @@ class WordZoomCenterWidget extends StatelessWidget {
super.key,
});
MessageOverlayController get overlayController =>
wordDetailsController.widget.overlayController;
PangeaToken get token => wordDetailsController.widget.token;
Widget content(BuildContext context, WordZoomSelection selectionType) {
@ -40,6 +43,7 @@ class WordZoomCenterWidget extends StatelessWidget {
token: token,
morphFeature: selectedMorphFeature!,
pangeaMessageEvent: wordDetailsController.widget.messageEvent,
overlayController: overlayController,
);
case WordZoomSelection.lemma:
return Text(token.lemma.text, textAlign: TextAlign.center);