feat: add openAI plugin
This commit is contained in:
parent
a9c3e3fdb6
commit
1de943e3e1
11 changed files with 145 additions and 20 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue