feat: tweaking morphogical edit flow and changing lemma meaning to edit flow
This commit is contained in:
parent
47930eea1b
commit
7054d92532
8 changed files with 169 additions and 72 deletions
|
|
@ -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?"
|
||||
}
|
||||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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, [
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue