fix: auto generator bugs (#1934)

This commit is contained in:
Lucas.Xu 2023-03-08 08:58:28 +08:00 committed by GitHub
parent 9e235c578e
commit e73870e6e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 32 deletions

View File

@ -1,6 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart'; import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'text_completion.dart'; import 'text_completion.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
@ -41,6 +43,17 @@ abstract class OpenAIRepository {
double temperature = .3, double temperature = .3,
}); });
Future<void> getStreamedCompletions({
required String prompt,
required Future<void> Function() onStart,
required Future<void> Function(TextCompletionResponse response) onProcess,
required VoidCallback onEnd,
required void Function(OpenAIError error) onError,
String? suffix,
int maxTokens = 500,
double temperature = 0.3,
});
/// Get edits from GPT-3 /// Get edits from GPT-3
/// ///
/// [input] is the input text /// [input] is the input text
@ -103,6 +116,74 @@ class HttpOpenAIRepository implements OpenAIRepository {
} }
} }
@override
Future<void> getStreamedCompletions({
required String prompt,
required Future<void> Function() onStart,
required Future<void> Function(TextCompletionResponse response) onProcess,
required VoidCallback onEnd,
required void Function(OpenAIError error) onError,
String? suffix,
int maxTokens = 500,
double temperature = 0.3,
}) async {
final parameters = {
'model': 'text-davinci-003',
'prompt': prompt,
'suffix': suffix,
'max_tokens': maxTokens,
'temperature': temperature,
'stream': true,
};
final request = http.Request('POST', OpenAIRequestType.textCompletion.uri);
request.headers.addAll(headers);
request.body = jsonEncode(parameters);
final response = await client.send(request);
// NEED TO REFACTOR.
// WHY OPENAI USE TWO LINES TO INDICATE THE START OF THE STREAMING RESPONSE?
// AND WHY OPENAI USE [DONE] TO INDICATE THE END OF THE STREAMING RESPONSE?
int syntax = 0;
var previousSyntax = '';
if (response.statusCode == 200) {
await for (final chunk in response.stream
.transform(const Utf8Decoder())
.transform(const LineSplitter())) {
syntax += 1;
if (syntax == 3) {
await onStart();
continue;
} else if (syntax < 3) {
continue;
}
final data = chunk.trim().split('data: ');
if (data.length > 1 && data[1] != '[DONE]') {
final response = TextCompletionResponse.fromJson(
json.decode(data[1]),
);
if (response.choices.isNotEmpty) {
final text = response.choices.first.text;
if (text == previousSyntax && text == '\n') {
continue;
}
await onProcess(response);
previousSyntax = response.choices.first.text;
Log.editor.info(response.choices.first.text);
}
} else {
onEnd();
}
}
} else {
final body = await response.stream.bytesToString();
onError(
OpenAIError.fromJson(json.decode(body)['error']),
);
}
}
@override @override
Future<Either<OpenAIError, TextEditResponse>> getEdits({ Future<Either<OpenAIError, TextEditResponse>> getEdits({
required String input, required String input,

View File

@ -8,7 +8,7 @@ class TextCompletionChoice with _$TextCompletionChoice {
required String text, required String text,
required int index, required int index,
// ignore: invalid_annotation_target // ignore: invalid_annotation_target
@JsonKey(name: 'finish_reason') required String finishReason, @JsonKey(name: 'finish_reason') String? finishReason,
}) = _TextCompletionChoice; }) = _TextCompletionChoice;
factory TextCompletionChoice.fromJson(Map<String, Object?> json) => factory TextCompletionChoice.fromJson(Map<String, Object?> json) =>

View File

@ -11,6 +11,10 @@ extension TextRobot on EditorState {
TextRobotInputType inputType = TextRobotInputType.word, TextRobotInputType inputType = TextRobotInputType.word,
Duration delay = const Duration(milliseconds: 10), Duration delay = const Duration(milliseconds: 10),
}) async { }) async {
if (text == '\n') {
await insertNewLineAtCurrentSelection();
return;
}
final lines = text.split('\n'); final lines = text.split('\n');
for (final line in lines) { for (final line in lines) {
if (line.isEmpty) { if (line.isEmpty) {
@ -28,13 +32,21 @@ extension TextRobot on EditorState {
} }
break; break;
case TextRobotInputType.word: case TextRobotInputType.word:
final words = line.split(' ').map((e) => '$e '); final words = line.split(' ');
for (final word in words) { if (words.length == 1 ||
(words.length == 2 &&
(words.first.isEmpty || words.last.isEmpty))) {
await insertTextAtCurrentSelection( await insertTextAtCurrentSelection(
word, line,
); );
await Future.delayed(delay, () {}); } else {
for (final word in words.map((e) => '$e ')) {
await insertTextAtCurrentSelection(
word,
);
}
} }
await Future.delayed(delay, () {});
break; break;
} }
} }

View File

@ -61,19 +61,14 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
void initState() { void initState() {
super.initState(); super.initState();
focusNode.addListener(() { textFieldFocusNode.addListener(_onFocusChanged);
if (focusNode.hasFocus) {
widget.editorState.service.selectionService.clearSelection();
} else {
widget.editorState.service.keyboardService?.enable();
}
});
textFieldFocusNode.requestFocus(); textFieldFocusNode.requestFocus();
} }
@override @override
void dispose() { void dispose() {
controller.dispose(); controller.dispose();
textFieldFocusNode.removeListener(_onFocusChanged);
super.dispose(); super.dispose();
} }
@ -242,30 +237,33 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
loading.start(); loading.start();
await _updateEditingText(); await _updateEditingText();
final result = await UserBackendService.getCurrentUserProfile(); final result = await UserBackendService.getCurrentUserProfile();
result.fold((userProfile) async { result.fold((userProfile) async {
final openAIRepository = HttpOpenAIRepository( final openAIRepository = HttpOpenAIRepository(
client: http.Client(), client: http.Client(),
apiKey: userProfile.openaiKey, apiKey: userProfile.openaiKey,
); );
final completions = await openAIRepository.getCompletions( await openAIRepository.getStreamedCompletions(
prompt: controller.text, prompt: controller.text,
onStart: () async {
loading.stop();
await _makeSurePreviousNodeIsEmptyTextNode();
},
onProcess: (response) async {
if (response.choices.isNotEmpty) {
final text = response.choices.first.text;
await widget.editorState.autoInsertText(
text,
inputType: TextRobotInputType.word,
);
}
},
onEnd: () {},
onError: (error) async {
loading.stop();
await _showError(error.message);
},
); );
completions.fold((error) async {
loading.stop();
await _showError(error.message);
}, (textCompletion) async {
loading.stop();
await _makeSurePreviousNodeIsEmptyTextNode();
// Open AI result uses two '\n' as the begin syntax.
var texts = textCompletion.choices.first.text.split('\n');
if (texts.length > 2) {
texts.removeRange(0, 2);
await widget.editorState.autoInsertText(
texts.join('\n'),
);
}
focusNode.requestFocus();
});
}, (error) async { }, (error) async {
loading.stop(); loading.stop();
await _showError( await _showError(
@ -345,4 +343,14 @@ class _AutoCompletionInputState extends State<_AutoCompletionInput> {
), ),
); );
} }
void _onFocusChanged() {
if (textFieldFocusNode.hasFocus) {
widget.editorState.service.keyboardService?.disable(
disposition: UnfocusDisposition.previouslyFocusedChild,
);
} else {
widget.editorState.service.keyboardService?.enable();
}
}
} }

View File

@ -35,7 +35,10 @@ abstract class AppFlowyKeyboardService {
/// you can disable the keyboard service of flowy_editor. /// you can disable the keyboard service of flowy_editor.
/// But you need to call the `enable` function to restore after exiting /// But you need to call the `enable` function to restore after exiting
/// your custom component, otherwise the keyboard service will fails. /// your custom component, otherwise the keyboard service will fails.
void disable({bool showCursor = false}); void disable({
bool showCursor = false,
UnfocusDisposition disposition = UnfocusDisposition.scope,
});
} }
/// Process keyboard events /// Process keyboard events
@ -102,10 +105,13 @@ class _AppFlowyKeyboardState extends State<AppFlowyKeyboard>
} }
@override @override
void disable({bool showCursor = false}) { void disable({
bool showCursor = false,
UnfocusDisposition disposition = UnfocusDisposition.scope,
}) {
isFocus = false; isFocus = false;
this.showCursor = showCursor; this.showCursor = showCursor;
_focusNode.unfocus(); _focusNode.unfocus(disposition: disposition);
} }
@override @override