[Scribe] Add Improve button (#4335)
This commit is contained in:
+1
-1
@@ -245,7 +245,7 @@ extension HandleAiScribeInComposerExtension on ComposerController {
|
|||||||
|
|
||||||
final fullText = await _getTextOnlyContentInEditor();
|
final fullText = await _getTextOnlyContentInEditor();
|
||||||
|
|
||||||
await AiScribeModalManager.showAIScribeMenuModal(
|
await AiScribeModalManager.showAIScribeModal(
|
||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
availableCategories: AIScribeMenuCategory.values,
|
availableCategories: AIScribeMenuCategory.values,
|
||||||
buttonPosition: position,
|
buttonPosition: position,
|
||||||
|
|||||||
@@ -40,12 +40,14 @@ export 'scribe/ai/presentation/widgets/items/ai_scribe_menu_submenu_icon.dart';
|
|||||||
export 'scribe/ai/presentation/widgets/items/ai_scribe_menu_text.dart';
|
export 'scribe/ai/presentation/widgets/items/ai_scribe_menu_text.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/ai_scribe_modal_widget.dart';
|
export 'scribe/ai/presentation/widgets/modal/ai_scribe_modal_widget.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart';
|
export 'scribe/ai/presentation/widgets/modal/ai_scribe_suggestion_widget.dart';
|
||||||
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_improve_button.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_error.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_error.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_header.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_header.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_loading.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_list_actions.dart';
|
||||||
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_toolbar.dart';
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_toolbar.dart';
|
||||||
|
export 'scribe/ai/presentation/widgets/modal/suggestion/ai_scribe_suggestion_success_actions.dart';
|
||||||
export 'scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart';
|
export 'scribe/ai/presentation/widgets/overlay/ai_selection_overlay.dart';
|
||||||
export 'scribe/ai/presentation/widgets/search/ai_scribe_bar.dart';
|
export 'scribe/ai/presentation/widgets/search/ai_scribe_bar.dart';
|
||||||
export 'scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart';
|
export 'scribe/ai/presentation/widgets/mobile/ai_scribe_mobile_actions_bottom_sheet.dart';
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "تحسين",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "إنشاء الاستجابة",
|
"generatingResponse": "إنشاء الاستجابة",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Verbessern",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Antwort wird generiert",
|
"generatingResponse": "Antwort wird generiert",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Improve",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Generating response",
|
"generatingResponse": "Generating response",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Améliorer",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Génération de la réponse",
|
"generatingResponse": "Génération de la réponse",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Migliora",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Generazione della risposta",
|
"generatingResponse": "Generazione della risposta",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"@@last_modified": "2026-02-04T15:15:31.191557",
|
"@@last_modified": "2026-02-09T16:58:15.727654",
|
||||||
"categoryCorrectGrammar": "Correct",
|
"categoryCorrectGrammar": "Correct",
|
||||||
"@categoryCorrectGrammar": {
|
"@categoryCorrectGrammar": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Improve",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"aiAssistant": "AI assistant",
|
"aiAssistant": "AI assistant",
|
||||||
"@aiAssistant": {
|
"@aiAssistant": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Улучшить",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Генерация ответа",
|
"generatingResponse": "Генерация ответа",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -102,6 +102,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"improve": "Cải thiện",
|
||||||
|
"@improve": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"generatingResponse": "Tạo phản hồi",
|
"generatingResponse": "Tạo phản hồi",
|
||||||
"@generatingResponse": {
|
"@generatingResponse": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -150,6 +150,13 @@ class ScribeLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get improve {
|
||||||
|
return Intl.message(
|
||||||
|
'Improve',
|
||||||
|
name: 'improve',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String get aiAssistant {
|
String get aiAssistant {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'AI assistant',
|
'AI assistant',
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ abstract final class AIScribeSizes {
|
|||||||
static const double scribeButtonRadius = 100;
|
static const double scribeButtonRadius = 100;
|
||||||
static const double aiAssistantIconRadius = 8;
|
static const double aiAssistantIconRadius = 8;
|
||||||
static const double contentCardRadius = 16;
|
static const double contentCardRadius = 16;
|
||||||
|
static const double improveButtonBorderRadius = 4;
|
||||||
|
|
||||||
// Width / height
|
// Width / height
|
||||||
static const double menuItemHeight = 40;
|
static const double menuItemHeight = 40;
|
||||||
@@ -162,6 +163,7 @@ abstract final class AIScribeSizes {
|
|||||||
static const double modalSpacing = 26;
|
static const double modalSpacing = 26;
|
||||||
static const double modalWithoutContentSpacing = 12;
|
static const double modalWithoutContentSpacing = 12;
|
||||||
static const double keyboardSpacing = 12;
|
static const double keyboardSpacing = 12;
|
||||||
|
static const double successSpacing = 12;
|
||||||
|
|
||||||
// Elevation
|
// Elevation
|
||||||
static const double dialogElevation = 8;
|
static const double dialogElevation = 8;
|
||||||
@@ -173,6 +175,10 @@ abstract final class AIScribeSizes {
|
|||||||
static const double aiAssistantIcon = 24;
|
static const double aiAssistantIcon = 24;
|
||||||
static const double bottomsheetIcon = 20;
|
static const double bottomsheetIcon = 20;
|
||||||
|
|
||||||
|
// Button sizes
|
||||||
|
static const double minButtonWidth = 72;
|
||||||
|
static const double buttonHeight = 36;
|
||||||
|
|
||||||
// Padding
|
// Padding
|
||||||
static const EdgeInsetsGeometry menuItemPadding =
|
static const EdgeInsetsGeometry menuItemPadding =
|
||||||
EdgeInsetsDirectional.only(start: 16, end: 10);
|
EdgeInsetsDirectional.only(start: 16, end: 10);
|
||||||
@@ -218,4 +224,7 @@ abstract final class AIScribeSizes {
|
|||||||
|
|
||||||
static const EdgeInsetsGeometry contentCardPadding =
|
static const EdgeInsetsGeometry contentCardPadding =
|
||||||
EdgeInsetsDirectional.all(16);
|
EdgeInsetsDirectional.all(16);
|
||||||
|
|
||||||
|
static const EdgeInsetsGeometry improveButtonPadding =
|
||||||
|
EdgeInsetsDirectional.only(start: 14, top: 6, end: 10, bottom: 6);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ class ContextSubmenuController {
|
|||||||
final clampedLeft = finalLeft
|
final clampedLeft = finalLeft
|
||||||
.clamp(0.0, math.max(0.0, screenWidth - submenuWidth))
|
.clamp(0.0, math.max(0.0, screenWidth - submenuWidth))
|
||||||
.toDouble();
|
.toDouble();
|
||||||
final availableHeight = math.max(0.0, screenHeight - anchor.top);
|
final availableHeight = math.max(0.0, screenHeight - bottom);
|
||||||
final finalHeight = math.min(submenuMaxHeight, availableHeight);
|
final finalHeight = math.min(submenuMaxHeight, availableHeight);
|
||||||
|
|
||||||
_submenuEntry = OverlayEntry(
|
_submenuEntry = OverlayEntry(
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:scribe/scribe.dart';
|
|||||||
class AiScribeModalManager {
|
class AiScribeModalManager {
|
||||||
AiScribeModalManager._();
|
AiScribeModalManager._();
|
||||||
|
|
||||||
static Future<void> showAIScribeMenuModal({
|
static Future<void> showAIScribeModal({
|
||||||
required bool isScribeMobile,
|
required bool isScribeMobile,
|
||||||
required ImagePaths imagePaths,
|
required ImagePaths imagePaths,
|
||||||
required List<AIScribeMenuCategory> availableCategories,
|
required List<AIScribeMenuCategory> availableCategories,
|
||||||
@@ -17,31 +17,16 @@ class AiScribeModalManager {
|
|||||||
ModalPlacement? preferredPlacement,
|
ModalPlacement? preferredPlacement,
|
||||||
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||||
}) async {
|
}) async {
|
||||||
final AIAction? aiAction;
|
final AIAction? aiAction = await showAIScribeMenuModal(
|
||||||
|
isScribeMobile: isScribeMobile,
|
||||||
if (isScribeMobile) {
|
|
||||||
aiAction = await showMobileAIScribeMenuModal(
|
|
||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
content: content,
|
content: content,
|
||||||
availableCategories: availableCategories,
|
availableCategories: availableCategories,
|
||||||
|
buttonPosition: buttonPosition,
|
||||||
|
buttonSize: buttonSize,
|
||||||
|
preferredPlacement: preferredPlacement,
|
||||||
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
final ContextSubmenuController submenuController = ContextSubmenuController();
|
|
||||||
|
|
||||||
aiAction = await Get.dialog<AIAction>(
|
|
||||||
AiScribeModalWidget(
|
|
||||||
imagePaths: imagePaths,
|
|
||||||
content: content,
|
|
||||||
availableCategories: availableCategories,
|
|
||||||
buttonPosition: buttonPosition,
|
|
||||||
buttonSize: buttonSize,
|
|
||||||
preferredPlacement: preferredPlacement,
|
|
||||||
crossAxisAlignment: crossAxisAlignment,
|
|
||||||
submenuController: submenuController,
|
|
||||||
),
|
|
||||||
barrierColor: AIScribeColors.dialogBarrier,
|
|
||||||
).whenComplete(submenuController.dispose);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aiAction != null) {
|
if (aiAction != null) {
|
||||||
await showAIScribeSuggestionModal(
|
await showAIScribeSuggestionModal(
|
||||||
@@ -58,6 +43,38 @@ class AiScribeModalManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<AIAction?> showAIScribeMenuModal({
|
||||||
|
required bool isScribeMobile,
|
||||||
|
required ImagePaths imagePaths,
|
||||||
|
required List<AIScribeMenuCategory> availableCategories,
|
||||||
|
String? content,
|
||||||
|
Offset? buttonPosition,
|
||||||
|
Size? buttonSize,
|
||||||
|
ModalPlacement? preferredPlacement,
|
||||||
|
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||||
|
bool showCustomPromptBar = true,
|
||||||
|
}) async {
|
||||||
|
if (isScribeMobile) {
|
||||||
|
return await showMobileAIScribeMenuModal(
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
content: content,
|
||||||
|
availableCategories: availableCategories,
|
||||||
|
showCustomPromptBar: showCustomPromptBar,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return await showDesktopAIScribeMenuModal(
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
content: content,
|
||||||
|
availableCategories: availableCategories,
|
||||||
|
buttonPosition: buttonPosition,
|
||||||
|
buttonSize: buttonSize,
|
||||||
|
preferredPlacement: preferredPlacement,
|
||||||
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
|
showCustomPromptBar: showCustomPromptBar,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> showAIScribeSuggestionModal({
|
static Future<void> showAIScribeSuggestionModal({
|
||||||
required AIAction aiAction,
|
required AIAction aiAction,
|
||||||
required bool isScribeMobile,
|
required bool isScribeMobile,
|
||||||
@@ -77,26 +94,52 @@ class AiScribeModalManager {
|
|||||||
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
await Get.dialog<String?>(
|
await showDesktopAIScribeSuggestionModal(
|
||||||
AiScribeSuggestionWidget(
|
aiAction: aiAction,
|
||||||
aiAction: aiAction,
|
imagePaths: imagePaths,
|
||||||
|
content: content,
|
||||||
|
buttonPosition: buttonPosition,
|
||||||
|
buttonSize: buttonSize,
|
||||||
|
preferredPlacement: preferredPlacement,
|
||||||
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
|
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<AIAction?> showDesktopAIScribeMenuModal({
|
||||||
|
required ImagePaths imagePaths,
|
||||||
|
required List<AIScribeMenuCategory> availableCategories,
|
||||||
|
String? content,
|
||||||
|
Offset? buttonPosition,
|
||||||
|
Size? buttonSize,
|
||||||
|
ModalPlacement? preferredPlacement,
|
||||||
|
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||||
|
bool showCustomPromptBar = true,
|
||||||
|
}) async {
|
||||||
|
final ContextSubmenuController submenuController = ContextSubmenuController();
|
||||||
|
|
||||||
|
return await Get.dialog<AIAction>(
|
||||||
|
AiScribeModalWidget(
|
||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
content: content,
|
content: content,
|
||||||
|
availableCategories: availableCategories,
|
||||||
buttonPosition: buttonPosition,
|
buttonPosition: buttonPosition,
|
||||||
buttonSize: buttonSize,
|
buttonSize: buttonSize,
|
||||||
preferredPlacement: preferredPlacement,
|
preferredPlacement: preferredPlacement,
|
||||||
crossAxisAlignment: crossAxisAlignment,
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
submenuController: submenuController,
|
||||||
|
showCustomPromptBar: showCustomPromptBar,
|
||||||
),
|
),
|
||||||
barrierColor: AIScribeColors.dialogBarrier,
|
barrierColor: AIScribeColors.dialogBarrier,
|
||||||
);
|
).whenComplete(submenuController.dispose);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<AIAction?> showMobileAIScribeMenuModal({
|
static Future<AIAction?> showMobileAIScribeMenuModal({
|
||||||
required ImagePaths imagePaths,
|
required ImagePaths imagePaths,
|
||||||
required List<AIScribeMenuCategory> availableCategories,
|
required List<AIScribeMenuCategory> availableCategories,
|
||||||
String? content,
|
String? content,
|
||||||
|
bool showCustomPromptBar = true,
|
||||||
}) async {
|
}) async {
|
||||||
final context = Get.context;
|
final context = Get.context;
|
||||||
|
|
||||||
@@ -110,10 +153,36 @@ class AiScribeModalManager {
|
|||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
availableCategories: availableCategories,
|
availableCategories: availableCategories,
|
||||||
content: content,
|
content: content,
|
||||||
|
showCustomPromptBar: showCustomPromptBar,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> showDesktopAIScribeSuggestionModal({
|
||||||
|
required AIAction aiAction,
|
||||||
|
required ImagePaths imagePaths,
|
||||||
|
required OnSelectAiScribeSuggestionAction onSelectAiScribeSuggestionAction,
|
||||||
|
String? content,
|
||||||
|
Offset? buttonPosition,
|
||||||
|
Size? buttonSize,
|
||||||
|
ModalPlacement? preferredPlacement,
|
||||||
|
ModalCrossAxisAlignment crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||||
|
}) async {
|
||||||
|
await Get.dialog<void>(
|
||||||
|
AiScribeSuggestionWidget(
|
||||||
|
aiAction: aiAction,
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
content: content,
|
||||||
|
buttonPosition: buttonPosition,
|
||||||
|
buttonSize: buttonSize,
|
||||||
|
preferredPlacement: preferredPlacement,
|
||||||
|
crossAxisAlignment: crossAxisAlignment,
|
||||||
|
onSelectAiScribeSuggestionAction: onSelectAiScribeSuggestionAction,
|
||||||
|
),
|
||||||
|
barrierColor: AIScribeColors.dialogBarrier,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Future<void> showMobileAIScribeSuggestionModal({
|
static Future<void> showMobileAIScribeSuggestionModal({
|
||||||
required AIAction aiAction,
|
required AIAction aiAction,
|
||||||
required ImagePaths imagePaths,
|
required ImagePaths imagePaths,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:get/get.dart';
|
|||||||
import 'package:scribe/scribe.dart';
|
import 'package:scribe/scribe.dart';
|
||||||
import 'package:scribe/scribe/ai/data/network/ai_api_exception.dart';
|
import 'package:scribe/scribe/ai/data/network/ai_api_exception.dart';
|
||||||
|
|
||||||
|
typedef OnLoadSuggestion = Future<void> Function([AIAction? aiAction, String? content]);
|
||||||
|
|
||||||
mixin AiScribeSuggestionStateMixin<T extends StatefulWidget> on State<T> {
|
mixin AiScribeSuggestionStateMixin<T extends StatefulWidget> on State<T> {
|
||||||
GenerateAITextInteractor? get interactor => _interactor;
|
GenerateAITextInteractor? get interactor => _interactor;
|
||||||
GenerateAITextInteractor? _interactor;
|
GenerateAITextInteractor? _interactor;
|
||||||
@@ -37,7 +39,10 @@ mixin AiScribeSuggestionStateMixin<T extends StatefulWidget> on State<T> {
|
|||||||
loadSuggestion();
|
loadSuggestion();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadSuggestion() async {
|
Future<void> loadSuggestion([AIAction? newAiAction, String? newContent]) async {
|
||||||
|
final aiActionToSend = newAiAction ?? aiAction;
|
||||||
|
final contentToSend = newContent ?? content;
|
||||||
|
|
||||||
_suggestionState.value = dartz.Right(GenerateAITextLoading());
|
_suggestionState.value = dartz.Right(GenerateAITextLoading());
|
||||||
|
|
||||||
if (_interactor == null) {
|
if (_interactor == null) {
|
||||||
@@ -50,8 +55,8 @@ mixin AiScribeSuggestionStateMixin<T extends StatefulWidget> on State<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final result = await _interactor!.execute(
|
final result = await _interactor!.execute(
|
||||||
aiAction,
|
aiActionToSend,
|
||||||
content,
|
contentToSend,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class InlineAiAssistButton extends StatelessWidget {
|
|||||||
|
|
||||||
final isScribeMobile = AiScribeMobileUtils.isScribeInMobileMode(context);
|
final isScribeMobile = AiScribeMobileUtils.isScribeInMobileMode(context);
|
||||||
|
|
||||||
await AiScribeModalManager.showAIScribeMenuModal(
|
await AiScribeModalManager.showAIScribeModal(
|
||||||
isScribeMobile: isScribeMobile,
|
isScribeMobile: isScribeMobile,
|
||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
availableCategories: AIScribeMenuCategory.values,
|
availableCategories: AIScribeMenuCategory.values,
|
||||||
|
|||||||
+6
@@ -9,12 +9,14 @@ class AiScribeMobileActionsBottomSheet extends StatefulWidget {
|
|||||||
final ImagePaths imagePaths;
|
final ImagePaths imagePaths;
|
||||||
final List<AIScribeMenuCategory> availableCategories;
|
final List<AIScribeMenuCategory> availableCategories;
|
||||||
final String? content;
|
final String? content;
|
||||||
|
final bool showCustomPromptBar;
|
||||||
|
|
||||||
const AiScribeMobileActionsBottomSheet({
|
const AiScribeMobileActionsBottomSheet({
|
||||||
super.key,
|
super.key,
|
||||||
required this.imagePaths,
|
required this.imagePaths,
|
||||||
required this.availableCategories,
|
required this.availableCategories,
|
||||||
this.content,
|
this.content,
|
||||||
|
this.showCustomPromptBar = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -153,6 +155,10 @@ class _AiScribeMobileActionsBottomSheetState
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBottomBar(BuildContext context) {
|
Widget _buildBottomBar(BuildContext context) {
|
||||||
|
if (!widget.showCustomPromptBar) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class AiScribeModalWidget extends StatelessWidget {
|
|||||||
final ModalPlacement? preferredPlacement;
|
final ModalPlacement? preferredPlacement;
|
||||||
final ModalCrossAxisAlignment crossAxisAlignment;
|
final ModalCrossAxisAlignment crossAxisAlignment;
|
||||||
final ContextSubmenuController? submenuController;
|
final ContextSubmenuController? submenuController;
|
||||||
|
final bool showCustomPromptBar;
|
||||||
|
|
||||||
const AiScribeModalWidget({
|
const AiScribeModalWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -25,6 +26,7 @@ class AiScribeModalWidget extends StatelessWidget {
|
|||||||
this.preferredPlacement,
|
this.preferredPlacement,
|
||||||
this.crossAxisAlignment = ModalCrossAxisAlignment.center,
|
this.crossAxisAlignment = ModalCrossAxisAlignment.center,
|
||||||
this.submenuController,
|
this.submenuController,
|
||||||
|
this.showCustomPromptBar = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -57,16 +59,17 @@ class AiScribeModalWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
MouseRegion(
|
if (showCustomPromptBar)
|
||||||
onEnter: (_) => submenuController?.hide(),
|
MouseRegion(
|
||||||
child: AIScribeBar(
|
onEnter: (_) => submenuController?.hide(),
|
||||||
imagePaths: imagePaths,
|
child: AIScribeBar(
|
||||||
onCustomPrompt: (customPrompt) {
|
imagePaths: imagePaths,
|
||||||
Navigator.of(context).pop(CustomPromptAction(customPrompt));
|
onCustomPrompt: (customPrompt) {
|
||||||
submenuController?.hide();
|
Navigator.of(context).pop(CustomPromptAction(customPrompt));
|
||||||
},
|
submenuController?.hide();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
+69
@@ -0,0 +1,69 @@
|
|||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:scribe/scribe.dart';
|
||||||
|
|
||||||
|
class AiScribeImproveButton extends StatefulWidget {
|
||||||
|
final ImagePaths imagePaths;
|
||||||
|
final String suggestionText;
|
||||||
|
final OnLoadSuggestion onLoadSuggestion;
|
||||||
|
|
||||||
|
const AiScribeImproveButton({
|
||||||
|
super.key,
|
||||||
|
required this.imagePaths,
|
||||||
|
required this.suggestionText,
|
||||||
|
required this.onLoadSuggestion,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AiScribeImproveButton> createState() => _AiScribeImproveButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AiScribeImproveButtonState extends State<AiScribeImproveButton> {
|
||||||
|
final GlobalKey _improveButtonKey = GlobalKey();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return TMailButtonWidget(
|
||||||
|
key: _improveButtonKey,
|
||||||
|
text: ScribeLocalizations.of(context).improve,
|
||||||
|
padding: AIScribeSizes.improveButtonPadding,
|
||||||
|
borderRadius: AIScribeSizes.improveButtonBorderRadius,
|
||||||
|
backgroundColor: AppColor.colorBackgroundTagFilter.withValues(alpha: 0.08),
|
||||||
|
icon: widget.imagePaths.icChevronDown,
|
||||||
|
iconColor: AppColor.colorTextButtonHeaderThread,
|
||||||
|
iconAlignment: TextDirection.rtl,
|
||||||
|
onTapActionCallback: _handleImproveButtonTap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleImproveButtonTap() async {
|
||||||
|
final renderBox = _improveButtonKey.currentContext?.findRenderObject();
|
||||||
|
Offset? position;
|
||||||
|
Size? size;
|
||||||
|
|
||||||
|
if (renderBox != null && renderBox is RenderBox) {
|
||||||
|
position = renderBox.localToGlobal(Offset.zero);
|
||||||
|
size = renderBox.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
final AIAction? aiAction = await AiScribeModalManager.showAIScribeMenuModal(
|
||||||
|
isScribeMobile: AiScribeMobileUtils.isScribeInMobileMode(context),
|
||||||
|
imagePaths: widget.imagePaths,
|
||||||
|
availableCategories: AIScribeMenuCategory.values,
|
||||||
|
content: widget.suggestionText,
|
||||||
|
buttonPosition: position,
|
||||||
|
buttonSize: size,
|
||||||
|
showCustomPromptBar: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (aiAction != null) {
|
||||||
|
widget.onLoadSuggestion(aiAction, widget.suggestionText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -7,7 +7,7 @@ class AiScribeSuggestionSuccess extends StatelessWidget {
|
|||||||
final String suggestionText;
|
final String suggestionText;
|
||||||
final bool hasContent;
|
final bool hasContent;
|
||||||
final OnSelectAiScribeSuggestionAction onSelectAction;
|
final OnSelectAiScribeSuggestionAction onSelectAction;
|
||||||
final VoidCallback onLoadSuggestion;
|
final OnLoadSuggestion onLoadSuggestion;
|
||||||
|
|
||||||
const AiScribeSuggestionSuccess({
|
const AiScribeSuggestionSuccess({
|
||||||
super.key,
|
super.key,
|
||||||
|
|||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
|
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:scribe/scribe.dart';
|
||||||
|
|
||||||
|
class AiScribeSuggestionSuccessActions extends StatelessWidget {
|
||||||
|
final ImagePaths imagePaths;
|
||||||
|
final String suggestionText;
|
||||||
|
final bool hasContent;
|
||||||
|
final OnSelectAiScribeSuggestionAction onSelectAction;
|
||||||
|
final OnLoadSuggestion onLoadSuggestion;
|
||||||
|
|
||||||
|
const AiScribeSuggestionSuccessActions({
|
||||||
|
super.key,
|
||||||
|
required this.imagePaths,
|
||||||
|
required this.suggestionText,
|
||||||
|
required this.onSelectAction,
|
||||||
|
required this.onLoadSuggestion,
|
||||||
|
this.hasContent = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
AiScribeImproveButton(
|
||||||
|
imagePaths: imagePaths,
|
||||||
|
suggestionText: suggestionText,
|
||||||
|
onLoadSuggestion: onLoadSuggestion,
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
if (hasContent) _buildReplaceButton(context),
|
||||||
|
_buildInsertButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildReplaceButton(BuildContext context) {
|
||||||
|
final localizations = ScribeLocalizations.of(context);
|
||||||
|
return Flexible(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(minWidth: AIScribeSizes.minButtonWidth),
|
||||||
|
height: AIScribeSizes.buttonHeight,
|
||||||
|
child: ConfirmDialogButton(
|
||||||
|
label: AiScribeSuggestionActions.replace.getLabel(localizations),
|
||||||
|
textColor: AppColor.primaryMain,
|
||||||
|
onTapAction: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onSelectAction(
|
||||||
|
AiScribeSuggestionActions.replace,
|
||||||
|
suggestionText,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInsertButton(BuildContext context) {
|
||||||
|
final localizations = ScribeLocalizations.of(context);
|
||||||
|
return Flexible(
|
||||||
|
child: Container(
|
||||||
|
constraints: const BoxConstraints(minWidth: AIScribeSizes.minButtonWidth),
|
||||||
|
height: AIScribeSizes.buttonHeight,
|
||||||
|
child: ConfirmDialogButton(
|
||||||
|
label: AiScribeSuggestionActions.insert.getLabel(localizations),
|
||||||
|
backgroundColor: AppColor.blueD2E9FF,
|
||||||
|
textColor: AppColor.primaryMain,
|
||||||
|
onTapAction: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
onSelectAction(
|
||||||
|
AiScribeSuggestionActions.insert,
|
||||||
|
suggestionText,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-46
@@ -1,6 +1,4 @@
|
|||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
|
||||||
import 'package:core/presentation/resources/image_paths.dart';
|
import 'package:core/presentation/resources/image_paths.dart';
|
||||||
import 'package:core/presentation/views/dialog/confirm_dialog_button.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:scribe/scribe.dart';
|
import 'package:scribe/scribe.dart';
|
||||||
|
|
||||||
@@ -14,7 +12,7 @@ class AiScribeSuggestionSuccessListActions extends StatelessWidget {
|
|||||||
final String suggestionText;
|
final String suggestionText;
|
||||||
final bool hasContent;
|
final bool hasContent;
|
||||||
final OnSelectAiScribeSuggestionAction onSelectAction;
|
final OnSelectAiScribeSuggestionAction onSelectAction;
|
||||||
final VoidCallback onLoadSuggestion;
|
final OnLoadSuggestion onLoadSuggestion;
|
||||||
|
|
||||||
const AiScribeSuggestionSuccessListActions({
|
const AiScribeSuggestionSuccessListActions({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -27,52 +25,16 @@ class AiScribeSuggestionSuccessListActions extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localizations = ScribeLocalizations.of(context);
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
spacing: AIScribeSizes.successSpacing,
|
||||||
children: [
|
children: [
|
||||||
AiScribeSuggestionSuccessToolbar(suggestionText: suggestionText, onLoadSuggestion: onLoadSuggestion, imagePaths: imagePaths),
|
AiScribeSuggestionSuccessToolbar(suggestionText: suggestionText, onLoadSuggestion: onLoadSuggestion, imagePaths: imagePaths),
|
||||||
Row(
|
AiScribeSuggestionSuccessActions(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
suggestionText: suggestionText,
|
||||||
spacing: 8,
|
onLoadSuggestion: onLoadSuggestion,
|
||||||
children: [
|
imagePaths: imagePaths,
|
||||||
if (hasContent)
|
hasContent: hasContent,
|
||||||
Flexible(
|
onSelectAction: onSelectAction,
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(minWidth: 67),
|
|
||||||
height: 36,
|
|
||||||
child: ConfirmDialogButton(
|
|
||||||
label: AiScribeSuggestionActions.replace.getLabel(localizations),
|
|
||||||
textColor: AppColor.primaryMain,
|
|
||||||
onTapAction: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onSelectAction(
|
|
||||||
AiScribeSuggestionActions.replace,
|
|
||||||
suggestionText,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Container(
|
|
||||||
constraints: const BoxConstraints(minWidth: 72),
|
|
||||||
height: 36,
|
|
||||||
child: ConfirmDialogButton(
|
|
||||||
label: AiScribeSuggestionActions.insert.getLabel(localizations),
|
|
||||||
backgroundColor: AppColor.blueD2E9FF,
|
|
||||||
textColor: AppColor.primaryMain,
|
|
||||||
onTapAction: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
onSelectAction(
|
|
||||||
AiScribeSuggestionActions.insert,
|
|
||||||
suggestionText,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import 'package:core/presentation/resources/image_paths.dart';
|
|||||||
|
|
||||||
class AiScribeSuggestionSuccessToolbar extends StatelessWidget {
|
class AiScribeSuggestionSuccessToolbar extends StatelessWidget {
|
||||||
final String suggestionText;
|
final String suggestionText;
|
||||||
final VoidCallback onLoadSuggestion;
|
final OnLoadSuggestion onLoadSuggestion;
|
||||||
final ImagePaths imagePaths;
|
final ImagePaths imagePaths;
|
||||||
|
|
||||||
const AiScribeSuggestionSuccessToolbar({
|
const AiScribeSuggestionSuccessToolbar({
|
||||||
|
|||||||
Reference in New Issue
Block a user