|
@@ -1,6 +1,8 @@
|
|
|
import 'dart:convert';
|
|
|
|
|
|
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 'package:dartz/dartz.dart';
|
|
@@ -41,6 +43,17 @@ abstract class OpenAIRepository {
|
|
|
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
|
|
|
///
|
|
|
/// [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
|
|
|
Future<Either<OpenAIError, TextEditResponse>> getEdits({
|
|
|
required String input,
|