Skip to content

Commit b0d525a

Browse files
authored
Merge pull request #2004 from AppFlowy-IO/chore/0.1.1
release 0.1.1
2 parents 120db68 + bf048ff commit b0d525a

File tree

16 files changed

+276
-120
lines changed

16 files changed

+276
-120
lines changed

frontend/Makefile.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
2323
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
2424
CARGO_MAKE_CRATE_NAME = "dart-ffi"
2525
LIB_NAME = "dart_ffi"
26-
CURRENT_APP_VERSION = "0.1.0"
26+
CURRENT_APP_VERSION = "0.1.1"
2727
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
2828
PRODUCT_NAME = "AppFlowy"
2929
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html

frontend/appflowy_flutter/assets/translations/en.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@
138138
"keep": "Keep",
139139
"tryAgain": "Try again",
140140
"discard": "Discard",
141-
"replace": "Replace"
141+
"replace": "Replace",
142+
"insertBelow": "Insert Below"
142143
},
143144
"label": {
144145
"welcome": "Welcome!",
@@ -342,8 +343,7 @@
342343
"plugins": {
343344
"referencedBoard": "Referenced Board",
344345
"referencedGrid": "Referenced Grid",
345-
"autoCompletionMenuItemName": "Auto Completion",
346-
"autoGeneratorMenuItemName": "Auto Generator",
346+
"autoGeneratorMenuItemName": "OpenAI Writer",
347347
"autoGeneratorTitleName": "OpenAI: Ask AI to write anything...",
348348
"autoGeneratorLearnMore": "Learn more",
349349
"autoGeneratorGenerate": "Generate",

frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/service/openai_client.dart

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'dart:convert';
22

33
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
4-
import 'package:appflowy_editor/appflowy_editor.dart';
54

65
import 'text_completion.dart';
76
import 'package:dartz/dartz.dart';
@@ -125,6 +124,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
125124
String? suffix,
126125
int maxTokens = 2048,
127126
double temperature = 0.3,
127+
bool useAction = false,
128128
}) async {
129129
final parameters = {
130130
'model': 'text-davinci-003',
@@ -151,14 +151,22 @@ class HttpOpenAIRepository implements OpenAIRepository {
151151
.transform(const Utf8Decoder())
152152
.transform(const LineSplitter())) {
153153
syntax += 1;
154-
if (syntax == 3) {
155-
await onStart();
156-
continue;
157-
} else if (syntax < 3) {
158-
continue;
154+
if (!useAction) {
155+
if (syntax == 3) {
156+
await onStart();
157+
continue;
158+
} else if (syntax < 3) {
159+
continue;
160+
}
161+
} else {
162+
if (syntax == 2) {
163+
await onStart();
164+
continue;
165+
} else if (syntax < 2) {
166+
continue;
167+
}
159168
}
160169
final data = chunk.trim().split('data: ');
161-
Log.editor.info(data.toString());
162170
if (data.length > 1) {
163171
if (data[1] != '[DONE]') {
164172
final response = TextCompletionResponse.fromJson(
@@ -173,7 +181,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
173181
previousSyntax = response.choices.first.text;
174182
}
175183
} else {
176-
onEnd();
184+
await onEnd();
177185
}
178186
}
179187
}
@@ -183,6 +191,7 @@ class HttpOpenAIRepository implements OpenAIRepository {
183191
OpenAIError.fromJson(json.decode(body)['error']),
184192
);
185193
}
194+
return;
186195
}
187196

188197
@override

frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/auto_completion_plugins.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/au
22
import 'package:appflowy_editor/appflowy_editor.dart';
33
import 'package:flutter/material.dart';
44

5+
import 'package:appflowy/generated/locale_keys.g.dart';
6+
import 'package:easy_localization/easy_localization.dart';
7+
58
SelectionMenuItem autoGeneratorMenuItem = SelectionMenuItem.node(
6-
name: 'Auto Generator',
9+
name: LocaleKeys.document_plugins_autoGeneratorMenuItemName.tr(),
710
iconData: Icons.generating_tokens,
8-
keywords: ['autogenerator', 'auto generator'],
11+
keywords: ['ai', 'openai' 'writer', 'autogenerator'],
912
nodeBuilder: (editorState) {
1013
final node = Node(
1114
type: kAutoCompletionInputType,

frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,30 @@ enum SmartEditAction {
1010
String get toInstruction {
1111
switch (this) {
1212
case SmartEditAction.summarize:
13-
return 'Make this shorter and more concise:';
13+
return 'Tl;dr';
1414
case SmartEditAction.fixSpelling:
1515
return 'Correct this to standard English:';
1616
}
1717
}
18+
19+
String prompt(String input) {
20+
switch (this) {
21+
case SmartEditAction.summarize:
22+
return '$input\n\nTl;dr';
23+
case SmartEditAction.fixSpelling:
24+
return 'Correct this to standard English:\n\n$input';
25+
}
26+
}
27+
28+
static SmartEditAction from(int index) {
29+
switch (index) {
30+
case 0:
31+
return SmartEditAction.summarize;
32+
case 1:
33+
return SmartEditAction.fixSpelling;
34+
}
35+
return SmartEditAction.fixSpelling;
36+
}
1837
}
1938

2039
class SmartEditActionWrapper extends ActionCell {

frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/openai/widgets/smart_edit_node_widget.dart

Lines changed: 102 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/error.dart';
21
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
3-
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_edit.dart';
42
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
53
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
64
import 'package:appflowy/user/application/user_service.dart';
@@ -12,8 +10,6 @@ import 'package:flutter/material.dart';
1210
import 'package:appflowy/generated/locale_keys.g.dart';
1311
import 'package:easy_localization/easy_localization.dart';
1412
import 'package:http/http.dart' as http;
15-
import 'package:dartz/dartz.dart' as dartz;
16-
import 'package:appflowy/util/either_extension.dart';
1713

1814
const String kSmartEditType = 'smart_edit_input';
1915
const String kSmartEditInstructionType = 'smart_edit_instruction';
@@ -22,9 +18,9 @@ const String kSmartEditInputType = 'smart_edit_input';
2218
class SmartEditInputBuilder extends NodeWidgetBuilder<Node> {
2319
@override
2420
NodeValidator<Node> get nodeValidator => (node) {
25-
return SmartEditAction.values.map((e) => e.toInstruction).contains(
26-
node.attributes[kSmartEditInstructionType],
27-
) &&
21+
return SmartEditAction.values
22+
.map((e) => e.index)
23+
.contains(node.attributes[kSmartEditInstructionType]) &&
2824
node.attributes[kSmartEditInputType] is String;
2925
};
3026

@@ -53,13 +49,14 @@ class _SmartEditInput extends StatefulWidget {
5349
}
5450

5551
class _SmartEditInputState extends State<_SmartEditInput> {
56-
String get instruction => widget.node.attributes[kSmartEditInstructionType];
52+
SmartEditAction get action =>
53+
SmartEditAction.from(widget.node.attributes[kSmartEditInstructionType]);
5754
String get input => widget.node.attributes[kSmartEditInputType];
5855

5956
final focusNode = FocusNode();
6057
final client = http.Client();
61-
dartz.Either<OpenAIError, TextEditResponse>? result;
6258
bool loading = true;
59+
String result = '';
6360

6461
@override
6562
void initState() {
@@ -72,12 +69,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
7269
widget.editorState.service.keyboardService?.enable();
7370
}
7471
});
75-
_requestEdits().then(
76-
(value) => setState(() {
77-
result = value;
78-
loading = false;
79-
}),
80-
);
72+
_requestCompletions();
8173
}
8274

8375
@override
@@ -141,25 +133,14 @@ class _SmartEditInputState extends State<_SmartEditInput> {
141133
child: const CircularProgressIndicator(),
142134
),
143135
);
144-
if (result == null) {
136+
if (result.isEmpty) {
145137
return loading;
146138
}
147-
return result!.fold((error) {
148-
return Flexible(
149-
child: Text(
150-
error.message,
151-
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
152-
color: Colors.red,
153-
),
154-
),
155-
);
156-
}, (response) {
157-
return Flexible(
158-
child: Text(
159-
response.choices.map((e) => e.text).join('\n'),
160-
),
161-
);
162-
});
139+
return Flexible(
140+
child: Text(
141+
result,
142+
),
143+
);
163144
}
164145

165146
Widget _buildInputFooterWidget(BuildContext context) {
@@ -174,8 +155,23 @@ class _SmartEditInputState extends State<_SmartEditInput> {
174155
),
175156
],
176157
),
177-
onPressed: () {
178-
_onReplace();
158+
onPressed: () async {
159+
await _onReplace();
160+
_onExit();
161+
},
162+
),
163+
const Space(10, 0),
164+
FlowyRichTextButton(
165+
TextSpan(
166+
children: [
167+
TextSpan(
168+
text: LocaleKeys.button_insertBelow.tr(),
169+
style: Theme.of(context).textTheme.bodyMedium,
170+
),
171+
],
172+
),
173+
onPressed: () async {
174+
await _onInsertBelow();
179175
_onExit();
180176
},
181177
),
@@ -201,12 +197,11 @@ class _SmartEditInputState extends State<_SmartEditInput> {
201197
final selectedNodes = widget
202198
.editorState.service.selectionService.currentSelectedNodes.normalized
203199
.whereType<TextNode>();
204-
if (selection == null || result == null || result!.isLeft()) {
200+
if (selection == null || result.isEmpty) {
205201
return;
206202
}
207203

208-
final texts = result!.asRight().choices.first.text.split('\n')
209-
..removeWhere((element) => element.isEmpty);
204+
final texts = result.split('\n')..removeWhere((element) => element.isEmpty);
210205
final transaction = widget.editorState.transaction;
211206
transaction.replaceTexts(
212207
selectedNodes.toList(growable: false),
@@ -216,6 +211,25 @@ class _SmartEditInputState extends State<_SmartEditInput> {
216211
return widget.editorState.apply(transaction);
217212
}
218213

214+
Future<void> _onInsertBelow() async {
215+
final selection = widget.editorState.service.selectionService
216+
.currentSelection.value?.normalized;
217+
if (selection == null || result.isEmpty) {
218+
return;
219+
}
220+
final texts = result.split('\n')..removeWhere((element) => element.isEmpty);
221+
final transaction = widget.editorState.transaction;
222+
transaction.insertNodes(
223+
selection.normalized.end.path.next,
224+
texts.map(
225+
(e) => TextNode(
226+
delta: Delta()..insert(e),
227+
),
228+
),
229+
);
230+
return widget.editorState.apply(transaction);
231+
}
232+
219233
Future<void> _onExit() async {
220234
final transaction = widget.editorState.transaction;
221235
transaction.deleteNode(widget.node);
@@ -228,35 +242,62 @@ class _SmartEditInputState extends State<_SmartEditInput> {
228242
);
229243
}
230244

231-
Future<dartz.Either<OpenAIError, TextEditResponse>> _requestEdits() async {
245+
Future<void> _requestCompletions() async {
232246
final result = await UserBackendService.getCurrentUserProfile();
233-
return result.fold((userProfile) async {
247+
return result.fold((l) async {
234248
final openAIRepository = HttpOpenAIRepository(
235249
client: client,
236-
apiKey: userProfile.openaiKey,
237-
);
238-
final edits = await openAIRepository.getEdits(
239-
input: input,
240-
instruction: instruction,
241-
n: 1,
250+
apiKey: l.openaiKey,
242251
);
243-
return edits.fold((error) async {
244-
return dartz.Left(
245-
OpenAIError(
246-
message:
247-
LocaleKeys.document_plugins_smartEditCouldNotFetchResult.tr(),
248-
),
252+
var lines = input.split('\n\n');
253+
if (action == SmartEditAction.summarize) {
254+
lines = [lines.join('\n')];
255+
}
256+
for (var i = 0; i < lines.length; i++) {
257+
final element = lines[i];
258+
await openAIRepository.getStreamedCompletions(
259+
useAction: true,
260+
prompt: action.prompt(element),
261+
onStart: () async {
262+
setState(() {
263+
loading = false;
264+
});
265+
},
266+
onProcess: (response) async {
267+
setState(() {
268+
this.result += response.choices.first.text;
269+
});
270+
},
271+
onEnd: () async {
272+
setState(() {
273+
if (i != lines.length - 1) {
274+
this.result += '\n';
275+
}
276+
});
277+
},
278+
onError: (error) async {
279+
await _showError(error.message);
280+
await _onExit();
281+
},
249282
);
250-
}, (textEdit) async {
251-
return dartz.Right(textEdit);
252-
});
253-
}, (error) async {
254-
// error
255-
return dartz.Left(
256-
OpenAIError(
257-
message: LocaleKeys.document_plugins_smartEditCouldNotFetchKey.tr(),
258-
),
259-
);
283+
}
284+
}, (r) async {
285+
await _showError(r.msg);
286+
await _onExit();
260287
});
261288
}
289+
290+
Future<void> _showError(String message) async {
291+
ScaffoldMessenger.of(context).showSnackBar(
292+
SnackBar(
293+
action: SnackBarAction(
294+
label: LocaleKeys.button_Cancel.tr(),
295+
onPressed: () {
296+
ScaffoldMessenger.of(context).hideCurrentSnackBar();
297+
},
298+
),
299+
content: FlowyText(message),
300+
),
301+
);
302+
}
262303
}

0 commit comments

Comments
 (0)