feat: add openAI plugin

This commit is contained in:
Lucas.Xu 2023-01-07 11:24:14 +08:00
commit 1de943e3e1
11 changed files with 145 additions and 20 deletions

View file

@ -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 ## 0.0.7
* Refactor theme customizer, and support dark mode. * Refactor theme customizer, and support dark mode.
* Support export and import markdown. * Support export and import markdown.

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:example/plugin/text_robot.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SimpleEditor extends StatelessWidget { class SimpleEditor extends StatelessWidget {
@ -64,6 +65,8 @@ class SimpleEditor extends StatelessWidget {
codeBlockMenuItem, codeBlockMenuItem,
// Emoji // Emoji
emojiMenuItem, emojiMenuItem,
// Text Robot
textRobotMenuItem,
], ],
); );
} else { } else {

View file

@ -0,0 +1,68 @@
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
Future<void> 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);
}
}
}

View file

@ -1,4 +1,57 @@
import 'package:appflowy_editor/appflowy_editor.dart'; 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 { enum TextRobotInputType {
character, character,
@ -19,31 +72,24 @@ class TextRobot {
TextRobotInputType inputType = TextRobotInputType.character, TextRobotInputType inputType = TextRobotInputType.character,
}) async { }) async {
final lines = text.split('\n'); final lines = text.split('\n');
var path = 0;
for (final line in lines) { for (final line in lines) {
switch (inputType) { switch (inputType) {
case TextRobotInputType.character: case TextRobotInputType.character:
var index = 0;
final iterator = line.runes.iterator; final iterator = line.runes.iterator;
while (iterator.moveNext()) { while (iterator.moveNext()) {
// await editorState.insertText(
// index,
// iterator.currentAsString,
// path: [path],
// );
await editorState.insertTextAtCurrentSelection( await editorState.insertTextAtCurrentSelection(
iterator.currentAsString, iterator.currentAsString,
); );
index += iterator.currentSize;
await Future.delayed(delay); await Future.delayed(delay);
} }
path += 1;
break; break;
default: default:
} }
// insert new line // insert new line
await editorState.insertNewLine(editorState, [path]); if (lines.length > 1) {
await editorState.insertNewLine(editorState);
}
} }
} }
} }

View file

@ -47,6 +47,7 @@ dependencies:
flutter_math_fork: ^0.6.3+1 flutter_math_fork: ^0.6.3+1
appflowy_editor_plugins: appflowy_editor_plugins:
path: ../../../packages/appflowy_editor_plugins path: ../../../packages/appflowy_editor_plugins
http: ^0.13.5
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -110,14 +110,15 @@ extension TextCommands on EditorState {
} }
Future<void> insertNewLine( Future<void> insertNewLine(
EditorState editorState, EditorState editorState, {
Path path, Path? path,
) async { }) async {
return futureCommand(() async { return futureCommand(() async {
final p = path ?? getSelection(null).start.path.next;
final transaction = editorState.transaction; final transaction = editorState.transaction;
transaction.insertNode(path, TextNode.empty()); transaction.insertNode(p, TextNode.empty());
transaction.afterSelection = Selection.single( transaction.afterSelection = Selection.single(
path: path, path: p,
startOffset: 0, startOffset: 0,
); );
apply(transaction); apply(transaction);

View file

@ -1,5 +1,4 @@
import 'package:appflowy_editor/appflowy_editor.dart'; 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/render/rich_text/built_in_text_widget.dart';
import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart';

View file

@ -96,7 +96,7 @@ class _SelectionMenuWidgetState extends State<SelectionMenuWidget> {
final items = widget.items final items = widget.items
.where( .where(
(item) => item.keywords.any((keyword) { (item) => item.keywords.any((keyword) {
final value = keyword.contains(newKeyword); final value = keyword.contains(newKeyword.toLowerCase());
if (value) { if (value) {
maxKeywordLength = max(maxKeywordLength, keyword.length); maxKeywordLength = max(maxKeywordLength, keyword.length);
} }

View file

@ -1,5 +1,4 @@
import 'package:appflowy_editor/appflowy_editor.dart'; 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/extensions/url_launcher_extension.dart';
import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/flutter/overlay.dart';
import 'package:appflowy_editor/src/infra/clipboard.dart'; import 'package:appflowy_editor/src/infra/clipboard.dart';

View file

@ -1,5 +1,4 @@
import 'package:appflowy_editor/appflowy_editor.dart'; 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/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View file

@ -1,6 +1,6 @@
name: appflowy_editor name: appflowy_editor
description: A highly customizable rich-text editor for Flutter description: A highly customizable rich-text editor for Flutter
version: 0.0.7 version: 0.0.9
homepage: https://github.com/AppFlowy-IO/AppFlowy homepage: https://github.com/AppFlowy-IO/AppFlowy
platforms: platforms: