Przeglądaj źródła

fix: #1962 Disable but still show the AI assistants icon in the toolbar menu when an OpenAI key is not provided

Lucas.Xu 2 lat temu
rodzic
commit
b7ec7ce32e

+ 2 - 0
frontend/appflowy_flutter/assets/translations/en.json

@@ -349,11 +349,13 @@
       "autoGeneratorGenerate": "Generate",
       "autoGeneratorHintText": "Tell us what you want to generate by OpenAI ...",
       "autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
+      "smartEdit": "Smart Edit",
       "smartEditTitleName": "Open AI: Smart Edit",
       "smartEditFixSpelling": "Fix spelling",
       "smartEditSummarize": "Summarize",
       "smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
       "smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
+      "smartEditDisabled": "Connect OpenAI in Settings",
       "cover": {
         "changeCover": "Change Cover",
         "colors": "Colors",

+ 1 - 3
frontend/appflowy_flutter/lib/plugins/document/document_page.dart

@@ -183,9 +183,7 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
         ],
       ],
       toolbarItems: [
-        if (openAIKey != null && openAIKey!.isNotEmpty) ...[
-          smartEditItem,
-        ]
+        smartEditItem,
       ],
       themeData: theme.copyWith(extensions: [
         ...theme.extensions.values,

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart

@@ -10,9 +10,9 @@ enum SmartEditAction {
   String get toInstruction {
     switch (this) {
       case SmartEditAction.summarize:
-        return 'Make it shorter';
+        return 'Make this shorter and more concise:';
       case SmartEditAction.fixSpelling:
-        return 'Fix all the spelling mistakes';
+        return 'Correct this to standard English:';
     }
   }
 }

+ 1 - 2
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart

@@ -222,7 +222,6 @@ class _SmartEditInputState extends State<_SmartEditInput> {
 
     final texts = result!.asRight().choices.first.text.split('\n')
       ..removeWhere((element) => element.isEmpty);
-    assert(texts.length == selectedNodes.length);
     final transaction = widget.editorState.transaction;
     transaction.replaceTexts(
       selectedNodes.toList(growable: false),
@@ -254,7 +253,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
       final edits = await openAIRepository.getEdits(
         input: input,
         instruction: instruction,
-        n: input.split('\n').length,
+        n: 1,
       );
       return edits.fold((error) async {
         return dartz.Left(

+ 40 - 2
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/smart_edit_toolbar_item.dart

@@ -1,10 +1,14 @@
 import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
 import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart';
+import 'package:appflowy/user/application/user_service.dart';
 import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
 import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:appflowy_popover/appflowy_popover.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flutter/material.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:easy_localization/easy_localization.dart';
 
 ToolbarItem smartEditItem = ToolbarItem(
   id: 'appflowy.toolbar.smart_edit',
@@ -33,6 +37,20 @@ class _SmartEditWidget extends StatefulWidget {
 }
 
 class _SmartEditWidgetState extends State<_SmartEditWidget> {
+  bool isOpenAIEnabled = false;
+
+  @override
+  void initState() {
+    super.initState();
+
+    UserBackendService.getCurrentUserProfile().then((value) {
+      setState(() {
+        isOpenAIEnabled =
+            value.fold((l) => l.openaiKey.isNotEmpty, (r) => false);
+      });
+    });
+  }
+
   @override
   Widget build(BuildContext context) {
     return PopoverActionList<SmartEditActionWrapper>(
@@ -43,7 +61,9 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
       buildChild: (controller) {
         return FlowyIconButton(
           hoverColor: Colors.transparent,
-          tooltipText: 'Smart Edit',
+          tooltipText: isOpenAIEnabled
+              ? LocaleKeys.document_plugins_smartEdit.tr()
+              : LocaleKeys.document_plugins_smartEditDisabled.tr(),
           preferBelow: false,
           icon: const Icon(
             Icons.lightbulb_outline,
@@ -51,7 +71,11 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
             color: Colors.white,
           ),
           onPressed: () {
-            controller.show();
+            if (isOpenAIEnabled) {
+              controller.show();
+            } else {
+              _showError(LocaleKeys.document_plugins_smartEditDisabled.tr());
+            }
           },
         );
       },
@@ -97,4 +121,18 @@ class _SmartEditWidgetState extends State<_SmartEditWidget> {
       withUpdateCursor: false,
     );
   }
+
+  Future<void> _showError(String message) async {
+    ScaffoldMessenger.of(context).showSnackBar(
+      SnackBar(
+        action: SnackBarAction(
+          label: LocaleKeys.button_Cancel.tr(),
+          onPressed: () {
+            ScaffoldMessenger.of(context).hideCurrentSnackBar();
+          },
+        ),
+        content: FlowyText(message),
+      ),
+    );
+  }
 }

+ 4 - 1
frontend/appflowy_flutter/packages/appflowy_editor/lib/src/commands/command_extension.dart

@@ -57,7 +57,10 @@ extension CommandExtension on EditorState {
     Selection selection,
   ) {
     List<String> res = [];
-    if (!selection.isCollapsed) {
+    if (selection.isSingle) {
+      final plainText = textNodes.first.toPlainText();
+      res.add(plainText.substring(selection.startIndex, selection.endIndex));
+    } else if (!selection.isCollapsed) {
       for (var i = 0; i < textNodes.length; i++) {
         final plainText = textNodes[i].toPlainText();
         if (i == 0) {

+ 88 - 14
frontend/appflowy_flutter/packages/appflowy_editor/lib/src/core/transform/transaction.dart

@@ -291,46 +291,120 @@ extension TextTransaction on Transaction {
     Selection selection,
     List<String> texts,
   ) {
-    if (textNodes.isEmpty) {
+    if (textNodes.isEmpty || texts.isEmpty) {
       return;
     }
 
-    if (selection.isSingle) {
-      assert(textNodes.length == 1 && texts.length == 1);
-      replaceText(
-        textNodes.first,
-        selection.startIndex,
-        selection.length,
-        texts.first,
-      );
-    } else {
+    if (textNodes.length == texts.length) {
       final length = textNodes.length;
-      for (var i = 0; i < length; i++) {
+
+      if (length == 1) {
+        replaceText(
+          textNodes.first,
+          selection.startIndex,
+          selection.endIndex - selection.startIndex,
+          texts.first,
+        );
+        return;
+      }
+
+      for (var i = 0; i < textNodes.length; i++) {
         final textNode = textNodes[i];
-        final text = texts[i];
         if (i == 0) {
           replaceText(
             textNode,
             selection.startIndex,
             textNode.toPlainText().length,
-            text,
+            texts.first,
           );
         } else if (i == length - 1) {
           replaceText(
             textNode,
             0,
             selection.endIndex,
-            text,
+            texts.last,
           );
         } else {
           replaceText(
             textNode,
             0,
             textNode.toPlainText().length,
+            texts[i],
+          );
+        }
+      }
+      return;
+    }
+
+    if (textNodes.length > texts.length) {
+      final length = textNodes.length;
+      for (var i = 0; i < textNodes.length; i++) {
+        final textNode = textNodes[i];
+        if (i == 0) {
+          replaceText(
+            textNode,
+            selection.startIndex,
+            textNode.toPlainText().length,
+            texts.first,
+          );
+        } else if (i == length - 1) {
+          replaceText(
+            textNode,
+            0,
+            selection.endIndex,
+            texts.last,
+          );
+        } else {
+          if (i < texts.length) {
+            replaceText(
+              textNode,
+              0,
+              textNode.toPlainText().length,
+              texts[i],
+            );
+          } else {
+            deleteNode(textNode);
+          }
+        }
+      }
+      return;
+    }
+
+    if (textNodes.length < texts.length) {
+      final length = texts.length;
+      for (var i = 0; i < texts.length; i++) {
+        final text = texts[i];
+        if (i == 0) {
+          replaceText(
+            textNodes.first,
+            selection.startIndex,
+            textNodes.first.toPlainText().length,
             text,
           );
+        } else if (i == length - 1) {
+          replaceText(
+            textNodes.last,
+            0,
+            selection.endIndex,
+            text,
+          );
+        } else {
+          if (i < textNodes.length) {
+            replaceText(
+              textNodes[i],
+              0,
+              textNodes[i].toPlainText().length,
+              texts[i],
+            );
+          } else {
+            insertNode(
+              textNodes.last.path,
+              TextNode(delta: Delta()..insert(text)),
+            );
+          }
         }
       }
+      return;
     }
   }
 }

+ 2 - 1
frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart

@@ -347,8 +347,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
   void _onPanStart(DragStartDetails details) {
     clearSelection();
+    _clearToolbar();
 
-    _panStartOffset = details.globalPosition.translate(-5.0, 0);
+    _panStartOffset = details.globalPosition.translate(-3.0, 0);
     _panStartScrollDy = editorState.service.scrollService?.dy;
 
     _enableInteraction();