mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: insert below and replace in smart-edit highlights text (#2107)
* feat: insert below and replace in smart-edit highlights text * test: added integration tests to validate insert below and replace in smart-edit highlights text * refactor: using get_it to inject OpenAiRepository to inject mock repo in test * fix: delete node does not propagate non null selection * refactor: suggested changes and fixed bugs causing warning in github-ci * fix: integration tests causing error in github-ci * refactor: reverting redundant changes due to recent changes in repo * refactor: reverting redundant changes due to recent changes in repo * refactor: refactoring to workspace based integration testing. * refactor: reverting redundant changes due to recent changes in repo * chore: fix analysis issues * chore: fix analysis issues * chore: remove the unnecessary conversion --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
f9095cfc64
commit
39b1ff0910
Binary file not shown.
@ -0,0 +1,108 @@
|
|||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||||
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'util/mock/mock_openai_repository.dart';
|
||||||
|
import 'util/util.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
const service = TestWorkspaceService(TestWorkspace.aiWorkSpace);
|
||||||
|
|
||||||
|
group('integration tests for open-ai smart menu', () {
|
||||||
|
setUpAll(() async => await service.setUpAll());
|
||||||
|
setUp(() async => await service.setUp());
|
||||||
|
|
||||||
|
testWidgets('testing selection on open-ai smart menu replace', (tester) async {
|
||||||
|
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||||
|
final editorState = appFlowyEditor.editorState;
|
||||||
|
|
||||||
|
editorState.service.selectionService.updateSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [1], offset: 4),
|
||||||
|
end: Position(path: [1], offset: 10),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('AI Assistants'));
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Summarize'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
editorState.service.selectionService.currentSelection.value,
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [1], offset: 4),
|
||||||
|
end: Position(path: [1], offset: 84),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
testWidgets('testing selection on open-ai smart menu insert', (tester) async {
|
||||||
|
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||||
|
final editorState = appFlowyEditor.editorState;
|
||||||
|
|
||||||
|
editorState.service.selectionService.updateSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [1], offset: 0),
|
||||||
|
end: Position(path: [1], offset: 5),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||||
|
|
||||||
|
await tester.tap(find.byTooltip('AI Assistants'));
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Summarize'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
editorState.service.selectionService.currentSelection.value,
|
||||||
|
Selection(
|
||||||
|
start: Position(path: [2], offset: 0),
|
||||||
|
end: Position(path: [3], offset: 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||||
|
await tester.initializeAppFlowy();
|
||||||
|
await mockOpenAIRepository();
|
||||||
|
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.backslash);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
final Finder editor = find.byType(AppFlowyEditor);
|
||||||
|
await tester.tap(editor);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
return (tester.state(editor).widget as AppFlowyEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> mockOpenAIRepository() async {
|
||||||
|
await getIt.unregister<OpenAIRepository>();
|
||||||
|
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||||
|
() => Future.value(
|
||||||
|
MockOpenAIRepository(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
|
|||||||
import 'board_test.dart' as board_test;
|
import 'board_test.dart' as board_test;
|
||||||
import 'switch_folder_test.dart' as switch_folder_test;
|
import 'switch_folder_test.dart' as switch_folder_test;
|
||||||
import 'empty_document_test.dart' as empty_document_test;
|
import 'empty_document_test.dart' as empty_document_test;
|
||||||
|
import 'open_ai_smart_menu_test.dart' as smart_menu_test;
|
||||||
|
|
||||||
/// The main task runner for all integration tests in AppFlowy.
|
/// The main task runner for all integration tests in AppFlowy.
|
||||||
///
|
///
|
||||||
@ -16,4 +17,5 @@ void main() {
|
|||||||
switch_folder_test.main();
|
switch_folder_test.main();
|
||||||
board_test.main();
|
board_test.main();
|
||||||
empty_document_test.main();
|
empty_document_test.main();
|
||||||
|
smart_menu_test.main();
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
enum TestWorkspace {
|
enum TestWorkspace {
|
||||||
board("board"),
|
board("board"),
|
||||||
emptyDocument("empty_document"),
|
emptyDocument("empty_document"),
|
||||||
|
aiWorkSpace("ai_workspace"),
|
||||||
coverImage("cover_image");
|
coverImage("cover_image");
|
||||||
|
|
||||||
const TestWorkspace(this._name);
|
const TestWorkspace(this._name);
|
||||||
|
@ -0,0 +1,76 @@
|
|||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_completion.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/error.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class MyMockClient extends Mock implements http.Client {
|
||||||
|
@override
|
||||||
|
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||||
|
final requestType = request.method;
|
||||||
|
final requestUri = request.url;
|
||||||
|
|
||||||
|
if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) {
|
||||||
|
final responseHeaders = <String, String>{'content-type': 'text/event-stream'};
|
||||||
|
final responseBody = Stream.fromIterable([
|
||||||
|
utf8.encode(
|
||||||
|
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
|
||||||
|
),
|
||||||
|
utf8.encode('\n'),
|
||||||
|
utf8.encode('[DONE]'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Return a mocked response with the expected data
|
||||||
|
return http.StreamedResponse(responseBody, 200, headers: responseHeaders);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return an error response for any other request
|
||||||
|
return http.StreamedResponse(const Stream.empty(), 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MockOpenAIRepository extends HttpOpenAIRepository {
|
||||||
|
MockOpenAIRepository() : super(apiKey: 'dummyKey', client: MyMockClient());
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> getStreamedCompletions({
|
||||||
|
required String prompt,
|
||||||
|
required Future<void> Function() onStart,
|
||||||
|
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||||
|
required Future<void> Function() onEnd,
|
||||||
|
required void Function(OpenAIError error) onError,
|
||||||
|
String? suffix,
|
||||||
|
int maxTokens = 2048,
|
||||||
|
double temperature = 0.3,
|
||||||
|
bool useAction = false,
|
||||||
|
}) async {
|
||||||
|
final request = http.Request('POST', OpenAIRequestType.textCompletion.uri);
|
||||||
|
final response = await client.send(request);
|
||||||
|
|
||||||
|
var previousSyntax = '';
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
await for (final chunk in response.stream.transform(const Utf8Decoder()).transform(const LineSplitter())) {
|
||||||
|
await onStart();
|
||||||
|
final data = chunk.trim().split('data: ');
|
||||||
|
if (data[0] != '[DONE]') {
|
||||||
|
final response = TextCompletionResponse.fromJson(
|
||||||
|
json.decode(data[0]),
|
||||||
|
);
|
||||||
|
if (response.choices.isNotEmpty) {
|
||||||
|
final text = response.choices.first.text;
|
||||||
|
if (text == previousSyntax && text == '\n') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await onProcess(response);
|
||||||
|
previousSyntax = response.choices.first.text;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await onEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@ -50,6 +50,7 @@ abstract class OpenAIRepository {
|
|||||||
String? suffix,
|
String? suffix,
|
||||||
int maxTokens = 2048,
|
int maxTokens = 2048,
|
||||||
double temperature = 0.3,
|
double temperature = 0.3,
|
||||||
|
bool useAction = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Get edits from GPT-3
|
/// Get edits from GPT-3
|
||||||
|
@ -4,7 +4,7 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/service/op
|
|||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/discard_dialog.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/discard_dialog.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -242,7 +242,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _onReplace();
|
await _onReplace();
|
||||||
_onExit();
|
await _onExit();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Space(10, 0),
|
const Space(10, 0),
|
||||||
@ -257,7 +257,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await _onInsertBelow();
|
await _onInsertBelow();
|
||||||
_onExit();
|
await _onExit();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Space(10, 0),
|
const Space(10, 0),
|
||||||
@ -272,10 +272,13 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
),
|
),
|
||||||
onPressed: () async => await _onExit(),
|
onPressed: () async => await _onExit(),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(flex: 2),
|
||||||
FlowyText.regular(
|
Expanded(
|
||||||
LocaleKeys.document_plugins_warning.tr(),
|
child: FlowyText.regular(
|
||||||
color: Theme.of(context).hintColor,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
LocaleKeys.document_plugins_warning.tr(),
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -298,7 +301,22 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
selection,
|
selection,
|
||||||
texts,
|
texts,
|
||||||
);
|
);
|
||||||
return widget.editorState.apply(transaction);
|
await widget.editorState.apply(transaction);
|
||||||
|
|
||||||
|
int endOffset = texts.last.length;
|
||||||
|
if (texts.length == 1) {
|
||||||
|
endOffset += selection.start.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
await widget.editorState.updateCursorSelection(
|
||||||
|
Selection(
|
||||||
|
start: selection.start,
|
||||||
|
end: Position(
|
||||||
|
path: [selection.start.path.first + texts.length - 1],
|
||||||
|
offset: endOffset,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onInsertBelow() async {
|
Future<void> _onInsertBelow() async {
|
||||||
@ -317,7 +335,16 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return widget.editorState.apply(transaction);
|
await widget.editorState.apply(transaction);
|
||||||
|
|
||||||
|
await widget.editorState.updateCursorSelection(
|
||||||
|
Selection(
|
||||||
|
start: Position(path: selection.end.path.next, offset: 0),
|
||||||
|
end: Position(
|
||||||
|
path: [selection.end.path.next.first + texts.length],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onExit() async {
|
Future<void> _onExit() async {
|
||||||
@ -333,51 +360,42 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _requestCompletions() async {
|
Future<void> _requestCompletions() async {
|
||||||
final result = await UserBackendService.getCurrentUserProfile();
|
final openAIRepository = await getIt.getAsync<OpenAIRepository>();
|
||||||
return result.fold((l) async {
|
|
||||||
final openAIRepository = HttpOpenAIRepository(
|
|
||||||
client: client,
|
|
||||||
apiKey: l.openaiKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
var lines = input.split('\n\n');
|
var lines = input.split('\n\n');
|
||||||
if (action == SmartEditAction.summarize) {
|
if (action == SmartEditAction.summarize) {
|
||||||
lines = [lines.join('\n')];
|
lines = [lines.join('\n')];
|
||||||
}
|
}
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
final element = lines[i];
|
final element = lines[i];
|
||||||
await openAIRepository.getStreamedCompletions(
|
await openAIRepository.getStreamedCompletions(
|
||||||
useAction: true,
|
useAction: true,
|
||||||
prompt: action.prompt(element),
|
prompt: action.prompt(element),
|
||||||
onStart: () async {
|
onStart: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
loading = false;
|
loading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onProcess: (response) async {
|
onProcess: (response) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (response.choices.first.text != '\n') {
|
if (response.choices.first.text != '\n') {
|
||||||
this.result += response.choices.first.text;
|
result += response.choices.first.text;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onEnd: () async {
|
onEnd: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (i != lines.length - 1) {
|
if (i != lines.length - 1) {
|
||||||
this.result += '\n';
|
result += '\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error) async {
|
onError: (error) async {
|
||||||
await _showError(error.message);
|
await _showError(error.message);
|
||||||
await _onExit();
|
await _onExit();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, (r) async {
|
|
||||||
await _showError(r.msg);
|
|
||||||
await _onExit();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showError(String message) async {
|
Future<void> _showError(String message) async {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle
|
|||||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||||
import 'package:appflowy/user/application/user_listener.dart';
|
import 'package:appflowy/user/application/user_listener.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
|
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
|
||||||
@ -27,6 +28,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class DependencyResolver {
|
class DependencyResolver {
|
||||||
static Future<void> resolve(GetIt getIt) async {
|
static Future<void> resolve(GetIt getIt) async {
|
||||||
@ -44,8 +46,25 @@ class DependencyResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resolveCommonService(GetIt getIt) {
|
void _resolveCommonService(GetIt getIt) async {
|
||||||
getIt.registerFactory<FilePickerService>(() => FilePicker());
|
getIt.registerFactory<FilePickerService>(() => FilePicker());
|
||||||
|
|
||||||
|
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||||
|
() async {
|
||||||
|
final result = await UserBackendService.getCurrentUserProfile();
|
||||||
|
return result.fold(
|
||||||
|
(l) {
|
||||||
|
return HttpOpenAIRepository(
|
||||||
|
client: http.Client(),
|
||||||
|
apiKey: l.openaiKey,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(r) {
|
||||||
|
throw Exception('Failed to get user profile: ${r.msg}');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _resolveUserDeps(GetIt getIt) {
|
void _resolveUserDeps(GetIt getIt) {
|
||||||
@ -160,4 +179,4 @@ void _resolveGridDeps(GetIt getIt) {
|
|||||||
(viewId, cache) =>
|
(viewId, cache) =>
|
||||||
DatabasePropertyBloc(viewId: viewId, fieldController: cache),
|
DatabasePropertyBloc(viewId: viewId, fieldController: cache),
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -809,7 +809,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
mocktail:
|
mocktail:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mocktail
|
name: mocktail
|
||||||
sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53"
|
sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53"
|
||||||
|
@ -42,7 +42,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
||||||
ref: a183c57
|
ref: a183c57
|
||||||
appflowy_editor: "^0.1.9"
|
appflowy_editor: ^0.1.9
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
|
|
||||||
@ -98,6 +98,7 @@ dependencies:
|
|||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
json_annotation: ^4.7.0
|
json_annotation: ^4.7.0
|
||||||
path: ^1.8.2
|
path: ^1.8.2
|
||||||
|
mocktail: ^0.3.0
|
||||||
archive: ^3.3.0
|
archive: ^3.3.0
|
||||||
flutter_svg: ^2.0.5
|
flutter_svg: ^2.0.5
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user