From 494e31993b6165d2c7a4225faeef4f4d4fb2aeb3 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 7 Jan 2023 11:24:14 +0800 Subject: [PATCH] feat: add openAI plugin --- .../packages/appflowy_editor/CHANGELOG.md | 9 +++ .../example/lib/pages/simple_editor.dart | 3 + .../lib/plugin/AI/getgpt3completions.dart | 68 +++++++++++++++++++ .../example/lib/plugin/text_robot.dart | 66 +++++++++++++++--- .../appflowy_editor/example/pubspec.yaml | 1 + .../lib/src/commands/text/text_commands.dart | 11 +-- .../src/render/rich_text/checkbox_text.dart | 1 - .../selection_menu/selection_menu_widget.dart | 2 +- .../lib/src/render/toolbar/toolbar_item.dart | 1 - .../space_on_web_handler.dart | 1 - .../packages/appflowy_editor/pubspec.yaml | 2 +- 11 files changed, 145 insertions(+), 20 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/AI/getgpt3completions.dart diff --git a/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md index d07a7b12fc..0e12ebf994 100644 --- a/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md @@ -1,3 +1,12 @@ +## 0.0.9 +* Support customize the text color and text background color. +* Fix some bugs. + +## 0.0.8 +* Fix the toolbar display issue. +* Fix the copy/paste issue on Windows. +* Minor Updates. + ## 0.0.7 * Refactor theme customizer, and support dark mode. * Support export and import markdown. diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart index 23fcca5af6..84397811da 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:example/plugin/text_robot.dart'; import 'package:flutter/material.dart'; class SimpleEditor extends StatelessWidget { @@ -64,6 +65,8 @@ class SimpleEditor extends StatelessWidget { codeBlockMenuItem, // Emoji emojiMenuItem, + // Text Robot + textRobotMenuItem, ], ); } else { diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/AI/getgpt3completions.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/AI/getgpt3completions.dart new file mode 100644 index 0000000000..e86bb71024 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/AI/getgpt3completions.dart @@ -0,0 +1,68 @@ +import 'package:http/http.dart' as http; +import 'dart:async'; +import 'dart:convert'; + +Future getGPT3Completion( + String apiKey, + String prompt, + String suffix, + int maxTokens, + double temperature, + Function(String) onData, // callback function to handle streaming data +) async { + final data = { + 'prompt': prompt, + 'suffix': suffix, + 'max_tokens': maxTokens, + 'temperature': temperature, + 'stream': true, // set stream parameter to true + }; + + final headers = { + 'Authorization': apiKey, + 'Content-Type': 'application/json', + }; + final request = http.Request( + 'POST', + Uri.parse('https://api.openai.com/v1/engines/text-davinci-003/completions'), + ); + request.body = json.encode(data); + request.headers.addAll(headers); + + final httpResponse = await request.send(); + + if (httpResponse.statusCode == 200) { + await for (final chunk in httpResponse.stream) { + var result = utf8.decode(chunk).split('text": "'); + var text = ''; + if (result.length > 1) { + result = result[1].split('",'); + if (result.isNotEmpty) { + text = result.first; + } + } + + final processedText = text + .replaceAll('\\n', '\n') + .replaceAll('\\r', '\r') + .replaceAll('\\t', '\t') + .replaceAll('\\b', '\b') + .replaceAll('\\f', '\f') + .replaceAll('\\v', '\v') + .replaceAll('\\\'', '\'') + .replaceAll('"', '"') + .replaceAll('\\0', '0') + .replaceAll('\\1', '1') + .replaceAll('\\2', '2') + .replaceAll('\\3', '3') + .replaceAll('\\4', '4') + .replaceAll('\\5', '5') + .replaceAll('\\6', '6') + .replaceAll('\\7', '7') + .replaceAll('\\8', '8') + .replaceAll('\\9', '9'); + + onData(processedText); + } + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/text_robot.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/text_robot.dart index 593bcb7042..538514fd43 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/text_robot.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/text_robot.dart @@ -1,4 +1,57 @@ import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:example/plugin/AI/getgpt3completions.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +SelectionMenuItem textRobotMenuItem = SelectionMenuItem( + name: () => 'Open AI', + icon: (editorState, onSelected) => Icon( + Icons.rocket, + size: 18.0, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, + ), + keywords: ['open ai', 'gpt3', 'ai'], + handler: ((editorState, menuService, context) async { + showDialog( + context: context, + builder: (context) { + final controller = TextEditingController(text: ''); + return AlertDialog( + content: RawKeyboardListener( + focusNode: FocusNode(), + child: TextField( + autofocus: true, + controller: controller, + maxLines: null, + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'Please input something...', + ), + ), + onKey: (key) { + if (key is! RawKeyDownEvent) return; + if (key.logicalKey == LogicalKeyboardKey.enter) { + Navigator.of(context).pop(); + // fetch the result and insert it + // Please fill in your own API key + getGPT3Completion('', controller.text, '', 200, .3, + (result) async { + await editorState.insertTextAtCurrentSelection( + result, + ); + }); + } else if (key.logicalKey == LogicalKeyboardKey.escape) { + Navigator.of(context).pop(); + } + }, + ), + ); + }, + ); + }), +); enum TextRobotInputType { character, @@ -19,31 +72,24 @@ class TextRobot { TextRobotInputType inputType = TextRobotInputType.character, }) async { final lines = text.split('\n'); - var path = 0; for (final line in lines) { switch (inputType) { case TextRobotInputType.character: - var index = 0; final iterator = line.runes.iterator; while (iterator.moveNext()) { - // await editorState.insertText( - // index, - // iterator.currentAsString, - // path: [path], - // ); await editorState.insertTextAtCurrentSelection( iterator.currentAsString, ); - index += iterator.currentSize; await Future.delayed(delay); } - path += 1; break; default: } // insert new line - await editorState.insertNewLine(editorState, [path]); + if (lines.length > 1) { + await editorState.insertNewLine(editorState); + } } } } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml index 885573b00a..ab0eb3aa7b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml @@ -47,6 +47,7 @@ dependencies: flutter_math_fork: ^0.6.3+1 appflowy_editor_plugins: path: ../../../packages/appflowy_editor_plugins + http: ^0.13.5 dev_dependencies: flutter_test: diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text/text_commands.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text/text_commands.dart index 0e2ec3abbc..6ee5778889 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text/text_commands.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/commands/text/text_commands.dart @@ -110,14 +110,15 @@ extension TextCommands on EditorState { } Future insertNewLine( - EditorState editorState, - Path path, - ) async { + EditorState editorState, { + Path? path, + }) async { return futureCommand(() async { + final p = path ?? getSelection(null).start.path.next; final transaction = editorState.transaction; - transaction.insertNode(path, TextNode.empty()); + transaction.insertNode(p, TextNode.empty()); transaction.afterSelection = Selection.single( - path: path, + path: p, startOffset: 0, ); apply(transaction); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart index 461fef0f30..d2b9cbbda2 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart @@ -1,5 +1,4 @@ import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/commands/text/text_commands.dart'; import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart index 7c7f86e6ab..d5181174cf 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart @@ -96,7 +96,7 @@ class _SelectionMenuWidgetState extends State { final items = widget.items .where( (item) => item.keywords.any((keyword) { - final value = keyword.contains(newKeyword); + final value = keyword.contains(newKeyword.toLowerCase()); if (value) { maxKeywordLength = max(maxKeywordLength, keyword.length); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index 6cdf729b11..4c30f1f9d3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -1,5 +1,4 @@ import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/commands/text/text_commands.dart'; import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart'; import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/infra/clipboard.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/space_on_web_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/space_on_web_handler.dart index a74e3e3c96..a69b2f4447 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/space_on_web_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/space_on_web_handler.dart @@ -1,5 +1,4 @@ import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/commands/text/text_commands.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml index 1602da9e06..67b6955541 100644 --- a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml @@ -1,6 +1,6 @@ name: appflowy_editor description: A highly customizable rich-text editor for Flutter -version: 0.0.7 +version: 0.0.9 homepage: https://github.com/AppFlowy-IO/AppFlowy platforms: