feat(ai-scribe): Fix the English locale file `(intl_en.arb)` is missing all three AI Scribe keys
feat(ai-scribe): Fix async function declared without proper return type annotation. feat(ai-scribe): Fix Async operations not properly awaited. feat(ai-scribe): Fix inconsistent capitalization between button labels. feat(ai-scribe): Fix GenerateAITextFailure check will never match. feat(ai-scribe): Fix consider validating text input for predefined actions. feat(ai-scribe): Using a subshell for directory isolation. feat(ai-scribe): Fix verify the mock usage or document intent. feat(ai-scribe): Using a more conventional import for Offset. feat(ai-scribe): Adding error handling for consistency feat(ai-scribe): Remove `fromJson` and `toJson` of AIResponse feat(ai-scribe): Using constants for role values. feat(ai-scribe): Using a more specific import. feat(ai-scribe): Using Timer for cleaner lifecycle management feat(ai-scribe): Adding error handling to fromMap() for consistency. feat(ai-scribe): Disposing the ValueNotifier feat(ai-scribe): Redundant null check after assignment feat(ai-scribe): Fix calling `registerSelectionChangeListener` multiple times may be inefficient. feat(ai-scribe): Safer type handling for JavaScript callback args feat(ai-scribe): Fix async callbacks not awaited in switch statement. feat(ai-scribe): Fix binding lifecycle mismatch for GetAIScribeConfigInteractor feat(ai-scribe): Fix ai prompts feat(ai-scribe): Fix unawaited async call to `_setupSelectionListener` feat(ai-scribe): Fix switch cases use top-origin coordinates while `PositionedDirectional(bottom:)`` expects bottom-origin coordinates. feat(ai-scribe): Adding error handling for selection listener setup.
This commit is contained in:
@@ -4,6 +4,8 @@ part 'ai_message.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AIMessage {
|
||||
static const String aiUserRole = 'user';
|
||||
|
||||
final String role;
|
||||
final String content;
|
||||
|
||||
@@ -18,7 +20,7 @@ class AIMessage {
|
||||
Map<String, dynamic> toJson() => _$AIMessageToJson(this);
|
||||
|
||||
factory AIMessage.ofUser(String content) => AIMessage(
|
||||
role: 'user',
|
||||
role: aiUserRole,
|
||||
content: content,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import 'package:scribe/scribe/ai/presentation/model/ai_action.dart';
|
||||
import 'package:scribe/scribe/ai/presentation/model/ai_scribe_menu_action.dart';
|
||||
|
||||
const PERFORM_TASK = "Perform only the following task:";
|
||||
const PRESERVE_LANGUAGE_PROMPT = "Do not translate. Strictly keep the original language of the input text. For example, if it's French, keep French. If it's English, keep English.";
|
||||
const DO_NOT_ADD_INFO_PROMPT = "Do not add any extra information or interpret anything beyond the explicit task.";
|
||||
|
||||
// TODO
|
||||
// In a near future, prompts will be loaded from a remote source
|
||||
class AIPrompts {
|
||||
static const _performTask = "Perform only the following task:";
|
||||
static const _preserveLanguagePrompt =
|
||||
"Do not translate. Strictly keep the original language of the input text. For example, if it's French, keep French. If it's English, keep English.";
|
||||
static const _doNotAddInfoPrompt =
|
||||
"Do not add any extra information or interpret anything beyond the explicit task.";
|
||||
|
||||
static String buildPrompt(AIAction action, String? text) {
|
||||
return switch (action) {
|
||||
PredefinedAction(action: final menuAction) =>
|
||||
buildPredefinedPrompt(menuAction, text ?? ''),
|
||||
text?.trim().isNotEmpty == true
|
||||
? buildPredefinedPrompt(menuAction, text!)
|
||||
: throw ArgumentError('Text cannot be empty for predefined actions'),
|
||||
CustomPromptAction(prompt: final customPrompt) =>
|
||||
buildCustomPrompt(customPrompt, text),
|
||||
};
|
||||
@@ -47,31 +49,31 @@ class AIPrompts {
|
||||
}
|
||||
|
||||
static String improveMakeShorter(String text) {
|
||||
return '$PERFORM_TASK make the text shorter but preserve the meaning. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask make the text shorter but preserve the meaning. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveExpandContext(String text) {
|
||||
return '$PERFORM_TASK expand the context of the text to make it more detailed and comprehensive. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask expand the context of the text to make it more detailed and comprehensive. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveEmojify(String text) {
|
||||
return '$PERFORM_TASK add emojis to the important parts of the text. Do not try to rephrase or replace text. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask add emojis to the important parts of the text. Do not try to rephrase or replace text. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveTransformToBullets(String text) {
|
||||
return '$PERFORM_TASK transform the text in a bullet list. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask transform the text into a bullet list. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String correctGrammar(String text) {
|
||||
return '$PERFORM_TASK correct grammar and spelling. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask correct grammar and spelling. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String changeToneTo(String text, String tone) {
|
||||
return '$PERFORM_TASK change the tone to be $tone. $PRESERVE_LANGUAGE_PROMPT $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask change the tone to be $tone. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String translateTo(String text, String language) {
|
||||
return '$PERFORM_TASK translate. Translate the text to the specified language: $language. $DO_NOT_ADD_INFO_PROMPT Text:\n\n$text';
|
||||
return '$_performTask translate. Translate the text to the specified language: $language. $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String buildCustomPrompt(String customPrompt, String? text) {
|
||||
|
||||
@@ -2,16 +2,4 @@ class AIResponse {
|
||||
final String result;
|
||||
|
||||
const AIResponse({required this.result});
|
||||
|
||||
factory AIResponse.fromJson(Map<String, dynamic> json) {
|
||||
return AIResponse(
|
||||
result: json['result'] as String? ?? json['text'] as String? ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'result': result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"replaceButton": "заменить",
|
||||
"replaceButton": "Заменить",
|
||||
"@replaceButton": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'dart:ui' show Offset;
|
||||
|
||||
class TextSelectionModel {
|
||||
final String? selectedText;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
typedef OnHoverShowSubmenu = void Function(GlobalKey key);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'dart:ui';
|
||||
import 'package:scribe/scribe.dart';
|
||||
|
||||
class AnchoredModalLayoutCalculator {
|
||||
@@ -201,21 +201,22 @@ class AnchoredModalLayoutCalculator {
|
||||
switch (placement) {
|
||||
case ModalPlacement.right:
|
||||
left = anchorPosition.dx + anchorSize.width + gap;
|
||||
bottom = anchorPosition.dy + verticalOffset;
|
||||
bottom = screenSize.height - (anchorPosition.dy + verticalOffset);
|
||||
break;
|
||||
|
||||
case ModalPlacement.bottom:
|
||||
left = anchorPosition.dx;
|
||||
bottom = anchorPosition.dy + anchorSize.height + gap + verticalOffset;
|
||||
bottom = screenSize.height - (anchorPosition.dy + anchorSize.height + gap + verticalOffset);
|
||||
break;
|
||||
|
||||
case ModalPlacement.top:
|
||||
left = anchorPosition.dx;
|
||||
bottom = anchorPosition.dy - menuSize.height - gap + verticalOffset;
|
||||
bottom = screenSize.height - (anchorPosition.dy - menuSize.height - gap + verticalOffset);
|
||||
break;
|
||||
|
||||
case ModalPlacement.left:
|
||||
left = anchorPosition.dx - menuSize.width - gap;
|
||||
bottom = anchorPosition.dy + verticalOffset;
|
||||
bottom = screenSize.height - (anchorPosition.dy + verticalOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
|
||||
widget.content,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
result.fold(
|
||||
(failure) => _state.value = dartz.Left(failure),
|
||||
(success) => _state.value = dartz.Right(success),
|
||||
@@ -203,9 +205,18 @@ class _AiScribeSuggestionWidgetState extends State<AiScribeSuggestionWidget> {
|
||||
widget.buttonPosition != null && widget.buttonSize != null;
|
||||
|
||||
void _handleClickOutside() {
|
||||
final result = _state.value.getOrElse(() => UIState.idle);
|
||||
if (result is GenerateAITextSuccess || result is GenerateAITextFailure) {
|
||||
final shouldDismiss = _state.value.fold(
|
||||
(failure) => failure is GenerateAITextFailure,
|
||||
(success) => success is GenerateAITextSuccess,
|
||||
);
|
||||
if (shouldDismiss) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_state.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -110,21 +112,18 @@ class _AnimatedEllipsisText extends StatefulWidget {
|
||||
class _AnimatedEllipsisTextState extends State<_AnimatedEllipsisText> {
|
||||
static const _dotStates = ['', '.', '..', '...'];
|
||||
int _index = 0;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tick();
|
||||
}
|
||||
|
||||
void _tick() {
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
_timer = Timer.periodic(const Duration(milliseconds: 500), (_) {
|
||||
if (!mounted) return;
|
||||
setState(() => _index = (_index + 1) % _dotStates.length);
|
||||
_tick();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
@@ -132,4 +131,10 @@ class _AnimatedEllipsisTextState extends State<_AnimatedEllipsisText> {
|
||||
style: widget.style,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user