From 7abe9f466105b33c6f2e0f36092aace231efaebd Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sat, 10 Aug 2024 17:23:37 +0800 Subject: [PATCH 01/28] feat: show indicator when send chat message with attachment/mention etc (#5919) * chore: adjust line height * chore: send stream message * chore: index file * chore: clippy --- .../base/type_option_menu_item.dart | 1 + .../view/edit_database_view_screen.dart | 1 + .../setting/font/font_setting.dart | 1 + .../setting/language_setting_group.dart | 1 + .../ai_chat/application/chat_bloc.dart | 105 ++++++++------ .../application/chat_message_stream.dart | 99 ++++++++++++++ .../application/chat_user_message_bloc.dart | 119 ++++++++++++++-- .../chat_user_message_bubble_bloc.dart | 48 +++++++ .../lib/plugins/ai_chat/chat_page.dart | 11 +- .../presentation/chat_input_action_menu.dart | 1 + .../presentation/message/ai_metadata.dart | 2 + .../presentation/message/ai_text_message.dart | 13 +- .../message/user_message_bubble.dart | 6 +- .../message/user_text_message.dart | 40 ++++-- .../widgets/board_column_header.dart | 1 + .../toolbar/calendar_layout_setting.dart | 12 +- .../widgets/calculations/calculate_cell.dart | 2 + .../calculations/calculation_type_item.dart | 6 +- .../widgets/filter/choicechip/choicechip.dart | 1 + .../widgets/filter/condition_button.dart | 1 + .../widgets/filter/create_filter_list.dart | 1 + .../widgets/filter/filter_menu.dart | 1 + .../widgets/footer/grid_footer.dart | 1 + .../widgets/header/desktop_field_cell.dart | 1 + .../widgets/header/grid_header.dart | 1 + .../grid/presentation/widgets/row/action.dart | 6 +- .../widgets/sort/create_sort_list.dart | 1 + .../widgets/sort/sort_choice_button.dart | 1 + .../tab_bar/desktop/tab_bar_header.dart | 1 + .../database/widgets/field/field_editor.dart | 3 + .../widgets/field/field_type_list.dart | 4 +- .../date/date_time_format.dart | 20 ++- .../field/type_option_editor/number.dart | 6 +- .../field/type_option_editor/relation.dart | 2 + .../select/select_option.dart | 1 + .../select/select_option_editor.dart | 2 + .../field/type_option_editor/translate.dart | 12 +- .../widgets/group/database_group.dart | 1 + .../database/widgets/row/row_action.dart | 10 +- .../database/widgets/row/row_banner.dart | 2 + .../database/widgets/row/row_property.dart | 7 +- .../setting/database_layout_selector.dart | 1 + .../setting/database_setting_action.dart | 1 + .../setting/setting_property_list.dart | 1 + .../base/selectable_item_list_menu.dart | 5 +- .../editor_plugins/file/file_upload_menu.dart | 1 - .../header/custom_cover_picker.dart | 1 + .../lib/plugins/shared/share/export_tab.dart | 5 +- .../lib/plugins/shared/share/publish_tab.dart | 1 + .../lib/plugins/trash/trash_page.dart | 10 +- .../menu/view/view_more_action_button.dart | 1 + .../setting_ai_view/settings_ai_view.dart | 2 - .../pages/settings_manage_data_view.dart | 1 - .../settings_plan_comparison_dialog.dart | 1 - .../flowy_infra_ui/lib/style_widget/text.dart | 10 +- frontend/appflowy_flutter/pubspec.lock | 8 +- frontend/rust-lib/flowy-ai/src/ai_manager.rs | 11 +- frontend/rust-lib/flowy-ai/src/chat.rs | 57 ++++++-- frontend/rust-lib/flowy-ai/src/entities.rs | 5 +- .../rust-lib/flowy-ai/src/event_handler.rs | 3 +- frontend/rust-lib/flowy-ai/src/lib.rs | 1 + .../flowy-ai/src/local_ai/local_llm_chat.rs | 128 ++++++++++++------ .../src/middleware/chat_service_mw.rs | 5 +- .../rust-lib/flowy-ai/src/stream_message.rs | 34 +++++ 64 files changed, 676 insertions(+), 172 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart create mode 100644 frontend/rust-lib/flowy-ai/src/stream_message.rs diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart index 497f769354..5e4595a1e5 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/type_option_menu_item.dart @@ -102,6 +102,7 @@ class _TypeOptionMenuItem extends StatelessWidget { value.text, fontSize: 14.0, maxLines: 2, + lineHeight: 1.0, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart index 4d8acbbeba..69ab500564 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart @@ -176,6 +176,7 @@ class DatabaseViewSettingTile extends StatelessWidget { return Row( children: [ FlowyText( + lineHeight: 1.0, databaseLayoutFromViewLayout(view.layout).layoutName, color: Theme.of(context).hintColor, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_setting.dart index 050bf4b594..1076b9dba6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_setting.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/font/font_setting.dart @@ -29,6 +29,7 @@ class FontSetting extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ FlowyText( + lineHeight: 1.0, name, color: theme.colorScheme.onSurface, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/language_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/language_setting_group.dart index 6f4e65f2b4..6473485514 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/language_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/language_setting_group.dart @@ -38,6 +38,7 @@ class _LanguageSettingGroupState extends State { mainAxisSize: MainAxisSize.min, children: [ FlowyText( + lineHeight: 1.0, languageFromLocale(locale), color: theme.colorScheme.onSurface, ), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart index 402243516d..cd5579d7d4 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart @@ -39,7 +39,8 @@ class ChatBloc extends Bloc { final String chatId; /// The last streaming message id - String lastStreamMessageId = ''; + String answerStreamMessageId = ''; + String questionStreamMessageId = ''; /// Using a temporary map to associate the real message ID with the last streaming message ID. /// @@ -127,21 +128,11 @@ class ChatBloc extends Bloc { ); }, // streaming message - streaming: (Message message) { - final allMessages = _perminentMessages(); - allMessages.insert(0, message); - emit( - state.copyWith( - messages: allMessages, - streamingState: const StreamingState.streaming(), - canSendMessage: false, - ), - ); - }, - finishStreaming: () { + finishAnswerStreaming: () { emit( state.copyWith( streamingState: const StreamingState.done(), + acceptRelatedQuestion: true, canSendMessage: state.sendingState == const SendMessageState.done(), ), @@ -162,9 +153,9 @@ class ChatBloc extends Bloc { // If the streaming is not started, remove the message from the list if (!state.answerStream!.hasStarted) { allMessages.removeWhere( - (element) => element.id == lastStreamMessageId, + (element) => element.id == answerStreamMessageId, ); - lastStreamMessageId = ""; + answerStreamMessageId = ""; } // when stop stream, we will set the answer stream to null. Which means the streaming @@ -189,22 +180,26 @@ class ChatBloc extends Bloc { ), ); }, + startAnswerStreaming: (Message message) { + final allMessages = _perminentMessages(); + allMessages.insert(0, message); + emit( + state.copyWith( + messages: allMessages, + streamingState: const StreamingState.streaming(), + canSendMessage: false, + ), + ); + }, sendMessage: (String message, Map? metadata) async { unawaited(_startStreamingMessage(message, metadata, emit)); final allMessages = _perminentMessages(); - // allMessages.insert( - // 0, - // CustomMessage( - // metadata: OnetimeShotType.sendingMessage.toMap(), - // author: User(id: state.userProfile.id.toString()), - // id: state.userProfile.id.toString(), - // ), - // ); emit( state.copyWith( lastSentMessage: null, messages: allMessages, relatedQuestions: [], + acceptRelatedQuestion: false, sendingState: const SendMessageState.sending(), canSendMessage: false, ), @@ -257,10 +252,17 @@ class ChatBloc extends Bloc { chatMessageCallback: (pb) { if (!isClosed) { // 3 mean message response from AI - if (pb.authorType == 3 && lastStreamMessageId.isNotEmpty) { + if (pb.authorType == 3 && answerStreamMessageId.isNotEmpty) { temporaryMessageIDMap[pb.messageId.toString()] = - lastStreamMessageId; - lastStreamMessageId = ""; + answerStreamMessageId; + answerStreamMessageId = ""; + } + + // 1 mean message response from User + if (pb.authorType == 1 && questionStreamMessageId.isNotEmpty) { + temporaryMessageIDMap[pb.messageId.toString()] = + questionStreamMessageId; + questionStreamMessageId = ""; } final message = _createTextMessage(pb); @@ -270,7 +272,7 @@ class ChatBloc extends Bloc { chatErrorMessageCallback: (err) { if (!isClosed) { Log.error("chat error: ${err.errorMessage}"); - add(const ChatEvent.finishStreaming()); + add(const ChatEvent.finishAnswerStreaming()); } }, latestMessageCallback: (list) { @@ -287,7 +289,7 @@ class ChatBloc extends Bloc { }, finishStreamingCallback: () { if (!isClosed) { - add(const ChatEvent.finishStreaming()); + add(const ChatEvent.finishAnswerStreaming()); // The answer strema will bet set to null after the streaming is finished or canceled. // so if the answer stream is null, we will not get related question. if (state.lastSentMessage != null && state.answerStream != null) { @@ -300,7 +302,9 @@ class ChatBloc extends Bloc { if (!isClosed) { result.fold( (list) { - add(ChatEvent.didReceiveRelatedQuestion(list.items)); + if (state.acceptRelatedQuestion) { + add(ChatEvent.didReceiveRelatedQuestion(list.items)); + } }, (err) { Log.error("Failed to get related question: $err"); @@ -358,16 +362,21 @@ class ChatBloc extends Bloc { } final answerStream = AnswerStream(); + final questionStream = QuestionStream(); add(ChatEvent.didUpdateAnswerStream(answerStream)); final payload = StreamChatPayloadPB( chatId: state.view.id, message: message, messageType: ChatMessageTypePB.User, - textStreamPort: Int64(answerStream.nativePort), + questionStreamPort: Int64(questionStream.nativePort), + answerStreamPort: Int64(answerStream.nativePort), metadata: await metadataPBFromMetadata(metadata), ); + final questionStreamMessage = _createQuestionStreamMessage(questionStream); + add(ChatEvent.receveMessage(questionStreamMessage)); + // Stream message to the server final result = await AIEventStreamMessage(payload).send(); result.fold( @@ -375,13 +384,12 @@ class ChatBloc extends Bloc { if (!isClosed) { add(ChatEvent.finishSending(question)); - final questionMessageId = question.messageId; - final message = _createTextMessage(question); - add(ChatEvent.receveMessage(message)); + // final message = _createTextMessage(question); + // add(ChatEvent.receveMessage(message)); final streamAnswer = - _createStreamMessage(answerStream, questionMessageId); - add(ChatEvent.streaming(streamAnswer)); + _createAnswerStreamMessage(answerStream, question.messageId); + add(ChatEvent.startAnswerStreaming(streamAnswer)); } }, (err) { @@ -404,9 +412,12 @@ class ChatBloc extends Bloc { ); } - Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) { + Message _createAnswerStreamMessage( + AnswerStream stream, + Int64 questionMessageId, + ) { final streamMessageId = (questionMessageId + 1).toString(); - lastStreamMessageId = streamMessageId; + answerStreamMessageId = streamMessageId; return TextMessage( author: User(id: "streamId:${nanoid()}"), @@ -421,6 +432,20 @@ class ChatBloc extends Bloc { ); } + Message _createQuestionStreamMessage(QuestionStream stream) { + questionStreamMessageId = nanoid(); + return TextMessage( + author: User(id: state.userProfile.id.toString()), + metadata: { + "$QuestionStream": stream, + "chatId": chatId, + }, + id: questionStreamMessageId, + createdAt: DateTime.now().millisecondsSinceEpoch, + text: '', + ); + } + Message _createTextMessage(ChatMessagePB message) { String messageId = message.messageId.toString(); @@ -454,9 +479,10 @@ class ChatEvent with _$ChatEvent { _FinishSendMessage; // receive message - const factory ChatEvent.streaming(Message message) = _StreamingMessage; + const factory ChatEvent.startAnswerStreaming(Message message) = + _StartAnswerStreaming; const factory ChatEvent.receveMessage(Message message) = _ReceiveMessage; - const factory ChatEvent.finishStreaming() = _FinishStreamingMessage; + const factory ChatEvent.finishAnswerStreaming() = _FinishAnswerStreaming; // loading messages const factory ChatEvent.startLoadingPrevMessage() = _StartLoadPrevMessage; @@ -499,6 +525,7 @@ class ChatState with _$ChatState { required bool hasMorePrevMessage, // The related questions that are received after the user message is sent. required List relatedQuestions, + @Default(false) bool acceptRelatedQuestion, // The last user message that is sent to the server. ChatMessagePB? lastSentMessage, AnswerStream? answerStream, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart index 3124e08c77..a82a84f34a 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart @@ -92,3 +92,102 @@ class AnswerStream { } } } + +class QuestionStream { + QuestionStream() { + _port.handler = _controller.add; + _subscription = _controller.stream.listen( + (event) { + if (event.startsWith("data:")) { + _hasStarted = true; + final newText = event.substring(5); + _text += newText; + if (_onData != null) { + _onData!(_text); + } + } else if (event.startsWith("message_id:")) { + final messageId = event.substring(11); + _onMessageId?.call(messageId); + } else if (event.startsWith("start_index_file:")) { + final indexName = event.substring(17); + _onFileIndexStart?.call(indexName); + } else if (event.startsWith("end_index_file:")) { + final indexName = event.substring(10); + _onFileIndexEnd?.call(indexName); + } else if (event.startsWith("index_file_error:")) { + final indexName = event.substring(16); + _onFileIndexError?.call(indexName); + } else if (event.startsWith("index_start:")) { + _onIndexStart?.call(); + } else if (event.startsWith("index_end:")) { + _onIndexEnd?.call(); + } else if (event.startsWith("done:")) { + _onDone?.call(); + } else if (event.startsWith("error:")) { + _error = event.substring(5); + if (_onError != null) { + _onError!(_error!); + } + } + }, + onError: (error) { + if (_onError != null) { + _onError!(error.toString()); + } + }, + ); + } + + final RawReceivePort _port = RawReceivePort(); + final StreamController _controller = StreamController.broadcast(); + late StreamSubscription _subscription; + bool _hasStarted = false; + String? _error; + String _text = ""; + + // Callbacks + void Function(String text)? _onData; + void Function(String error)? _onError; + void Function(String messageId)? _onMessageId; + void Function(String indexName)? _onFileIndexStart; + void Function(String indexName)? _onFileIndexEnd; + void Function(String indexName)? _onFileIndexError; + void Function()? _onIndexStart; + void Function()? _onIndexEnd; + void Function()? _onDone; + + int get nativePort => _port.sendPort.nativePort; + bool get hasStarted => _hasStarted; + String? get error => _error; + String get text => _text; + + Future dispose() async { + await _controller.close(); + await _subscription.cancel(); + _port.close(); + } + + void listen({ + void Function(String text)? onData, + void Function(String error)? onError, + void Function(String messageId)? onMessageId, + void Function(String indexName)? onFileIndexStart, + void Function(String indexName)? onFileIndexEnd, + void Function(String indexName)? onFileIndexFail, + void Function()? onIndexStart, + void Function()? onIndexEnd, + void Function()? onDone, + }) { + _onData = onData; + _onError = onError; + _onMessageId = onMessageId; + + _onFileIndexStart = onFileIndexStart; + _onFileIndexEnd = onFileIndexEnd; + _onFileIndexError = onFileIndexFail; + + _onIndexStart = onIndexStart; + _onIndexEnd = onIndexEnd; + _onDone = onDone; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart index 41fea4ab22..207d811b8b 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart @@ -1,27 +1,91 @@ -import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_message_stream.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_chat_types/flutter_chat_types.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'chat_message_service.dart'; - part 'chat_user_message_bloc.freezed.dart'; class ChatUserMessageBloc extends Bloc { ChatUserMessageBloc({ - required Message message, - required String? metadata, + required dynamic message, }) : super( ChatUserMessageState.initial( message, - chatFilesFromMetadataString(metadata), ), ) { on( (event, emit) async { event.when( - initial: () {}, + initial: () { + if (state.stream != null) { + add(ChatUserMessageEvent.updateText(state.stream!.text)); + } + + state.stream?.listen( + onData: (text) { + if (!isClosed) { + add(ChatUserMessageEvent.updateText(text)); + } + }, + onMessageId: (messageId) { + if (!isClosed) { + add(ChatUserMessageEvent.updateMessageId(messageId)); + } + }, + onError: (error) { + if (!isClosed) { + add(ChatUserMessageEvent.receiveError(error.toString())); + } + }, + onFileIndexStart: (indexName) { + Log.debug("index start: $indexName"); + }, + onFileIndexEnd: (indexName) { + Log.info("index end: $indexName"); + }, + onFileIndexFail: (indexName) { + Log.debug("index fail: $indexName"); + }, + onIndexStart: () { + if (!isClosed) { + add( + const ChatUserMessageEvent.updateQuestionState( + QuestionMessageState.indexStart(), + ), + ); + } + }, + onIndexEnd: () { + if (!isClosed) { + add( + const ChatUserMessageEvent.updateQuestionState( + QuestionMessageState.indexEnd(), + ), + ); + } + }, + onDone: () { + if (!isClosed) { + add( + const ChatUserMessageEvent.updateQuestionState( + QuestionMessageState.finish(), + ), + ); + } + }, + ); + }, + updateText: (String text) { + emit(state.copyWith(text: text)); + }, + updateMessageId: (String messageId) { + emit(state.copyWith(messageId: messageId)); + }, + receiveError: (String error) {}, + updateQuestionState: (QuestionMessageState newState) { + emit(state.copyWith(messageState: newState)); + }, ); }, ); @@ -31,18 +95,47 @@ class ChatUserMessageBloc @freezed class ChatUserMessageEvent with _$ChatUserMessageEvent { const factory ChatUserMessageEvent.initial() = Initial; + const factory ChatUserMessageEvent.updateText(String text) = _UpdateText; + const factory ChatUserMessageEvent.updateQuestionState( + QuestionMessageState newState, + ) = _UpdateQuestionState; + const factory ChatUserMessageEvent.updateMessageId(String messageId) = + _UpdateMessageId; + const factory ChatUserMessageEvent.receiveError(String error) = _ReceiveError; } @freezed class ChatUserMessageState with _$ChatUserMessageState { const factory ChatUserMessageState({ - required Message message, - required List files, + required String text, + QuestionStream? stream, + String? messageId, + @Default(QuestionMessageState.finish()) QuestionMessageState messageState, }) = _ChatUserMessageState; factory ChatUserMessageState.initial( - Message message, - List files, + dynamic message, ) => - ChatUserMessageState(message: message, files: files); + ChatUserMessageState( + text: message is String ? message : "", + stream: message is QuestionStream ? message : null, + ); +} + +@freezed +class QuestionMessageState with _$QuestionMessageState { + const factory QuestionMessageState.indexFileStart(String fileName) = + _IndexFileStart; + const factory QuestionMessageState.indexFileEnd(String fileName) = + _IndexFileEnd; + const factory QuestionMessageState.indexFileFail(String fileName) = + _IndexFileFail; + + const factory QuestionMessageState.indexStart() = _IndexStart; + const factory QuestionMessageState.indexEnd() = _IndexEnd; + const factory QuestionMessageState.finish() = _Finish; +} + +extension QuestionMessageStateX on QuestionMessageState { + bool get isFinish => this is _Finish; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart new file mode 100644 index 0000000000..542280ca99 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart @@ -0,0 +1,48 @@ +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_chat_types/flutter_chat_types.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'chat_message_service.dart'; + +part 'chat_user_message_bubble_bloc.freezed.dart'; + +class ChatUserMessageBubbleBloc + extends Bloc { + ChatUserMessageBubbleBloc({ + required Message message, + required String? metadata, + }) : super( + ChatUserMessageBubbleState.initial( + message, + chatFilesFromMetadataString(metadata), + ), + ) { + on( + (event, emit) async { + event.when( + initial: () {}, + ); + }, + ); + } +} + +@freezed +class ChatUserMessageBubbleEvent with _$ChatUserMessageBubbleEvent { + const factory ChatUserMessageBubbleEvent.initial() = Initial; +} + +@freezed +class ChatUserMessageBubbleState with _$ChatUserMessageBubbleState { + const factory ChatUserMessageBubbleState({ + required Message message, + required List files, + }) = _ChatUserMessageBubbleState; + + factory ChatUserMessageBubbleState.initial( + Message message, + List files, + ) => + ChatUserMessageBubbleState(message: message, files: files); +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart index a2a0618e41..6ad35d08db 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -319,21 +319,22 @@ class _ChatContentPageState extends State<_ChatContentPage> { Widget _buildTextMessage(BuildContext context, TextMessage message) { if (message.author.id == _user.id) { + final stream = message.metadata?["$QuestionStream"]; final metadata = message.metadata?[messageMetadataKey] as String?; - return ChatUserTextMessageWidget( + return ChatUserMessageWidget( + key: ValueKey(message.id), user: message.author, - messageUserId: message.id, - message: message, + message: stream is QuestionStream ? stream : message.text, metadata: metadata, ); } else { final stream = message.metadata?["$AnswerStream"]; final questionId = message.metadata?[messageQuestionIdKey]; final metadata = message.metadata?[messageMetadataKey] as String?; - return ChatAITextMessageWidget( + return ChatAIMessageWidget( user: message.author, messageUserId: message.id, - text: stream is AnswerStream ? stream : message.text, + message: stream is AnswerStream ? stream : message.text, key: ValueKey(message.id), questionId: questionId, chatId: widget.view.id, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input_action_menu.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input_action_menu.dart index faf586e3b9..2378379e6e 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input_action_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input_action_menu.dart @@ -156,6 +156,7 @@ class _ActionItem extends StatelessWidget { margin: const EdgeInsets.symmetric(horizontal: 6), iconPadding: 10.0, text: FlowyText.regular( + lineHeight: 1.0, item.title, ), onTap: onTap, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart index 77ee0b5e76..9796a28a34 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart @@ -49,6 +49,8 @@ class AIMessageMetadata extends StatelessWidget { child: FlowyText( m.name, fontSize: 14, + lineHeight: 1.0, + overflow: TextOverflow.ellipsis, ), ), onTap: () => onSelectedMetadata(m), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart index eaf746cae0..ed928eba14 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart @@ -14,12 +14,12 @@ import 'package:flutter_chat_types/flutter_chat_types.dart'; import 'ai_metadata.dart'; -class ChatAITextMessageWidget extends StatelessWidget { - const ChatAITextMessageWidget({ +class ChatAIMessageWidget extends StatelessWidget { + const ChatAIMessageWidget({ super.key, required this.user, required this.messageUserId, - required this.text, + required this.message, required this.questionId, required this.chatId, required this.metadata, @@ -28,7 +28,9 @@ class ChatAITextMessageWidget extends StatelessWidget { final User user; final String messageUserId; - final dynamic text; + + /// message can be a striing or Stream + final dynamic message; final Int64? questionId; final String chatId; final String? metadata; @@ -38,7 +40,7 @@ class ChatAITextMessageWidget extends StatelessWidget { Widget build(BuildContext context) { return BlocProvider( create: (context) => ChatAIMessageBloc( - message: text, + message: message, metadata: metadata, chatId: chatId, questionId: questionId, @@ -59,7 +61,6 @@ class ChatAITextMessageWidget extends StatelessWidget { return FlowyText( LocaleKeys.sideBar_askOwnerToUpgradeToAIMax.tr(), maxLines: 10, - lineHeight: 1.5, ); }, ready: () { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart index 6f60442a16..06401b76a7 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart @@ -1,6 +1,6 @@ import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_member_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -32,11 +32,11 @@ class ChatUserMessageBubble extends StatelessWidget { final metadata = message.metadata?[messageMetadataKey] as String?; return BlocProvider( - create: (context) => ChatUserMessageBloc( + create: (context) => ChatUserMessageBubbleBloc( message: message, metadata: metadata, ), - child: BlocBuilder( + child: BlocBuilder( builder: (context, state) { return Column( mainAxisSize: MainAxisSize.min, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart index 3e37b12dd3..0b2a2efa7d 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart @@ -1,26 +1,50 @@ +import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bloc.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; -class ChatUserTextMessageWidget extends StatelessWidget { - const ChatUserTextMessageWidget({ +class ChatUserMessageWidget extends StatelessWidget { + const ChatUserMessageWidget({ super.key, required this.user, - required this.messageUserId, required this.message, required this.metadata, }); final User user; - final String messageUserId; - final TextMessage message; + final dynamic message; final String? metadata; @override Widget build(BuildContext context) { - return TextMessageText( - text: message.text, + return BlocProvider( + create: (context) => ChatUserMessageBloc(message: message) + ..add(const ChatUserMessageEvent.initial()), + child: BlocBuilder( + builder: (context, state) { + final List children = []; + children.add( + Flexible( + child: TextMessageText( + text: state.text, + ), + ), + ); + + if (!state.messageState.isFinish) { + children.add(const HSpace(6)); + children.add(const CircularProgressIndicator.adaptive()); + } + + return Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: children, + ); + }, + ), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart index b74f83cebc..0ee31e41f4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_column_header.dart @@ -237,6 +237,7 @@ class _BoardColumnHeaderState extends State { leftIcon: FlowySvg(action.icon), text: FlowyText.medium( action.text, + lineHeight: 1.0, overflow: TextOverflow.ellipsis, ), onTap: () { diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart index 6cbf39e9e3..816f553704 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart @@ -173,7 +173,10 @@ class LayoutDateField extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(fieldInfo.name), + text: FlowyText.medium( + fieldInfo.name, + lineHeight: 1.0, + ), onTap: () { onUpdated(fieldInfo.id); popoverMutex.close(); @@ -206,6 +209,7 @@ class LayoutDateField extends StatelessWidget { child: FlowyButton( margin: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 10.0), text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.calendar_settings_layoutDateField.tr(), ), ), @@ -307,6 +311,7 @@ class FirstDayOfWeek extends StatelessWidget { child: FlowyButton( margin: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 10.0), text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.calendar_settings_firstDayOfWeek.tr(), ), ), @@ -367,7 +372,10 @@ class StartFromButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(title), + text: FlowyText.medium( + title, + lineHeight: 1.0, + ), onTap: () => onTap(dayIndex), rightIcon: isSelected ? const FlowySvg(FlowySvgs.check_s) : null, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart index ebf655552c..1040081d51 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart @@ -167,6 +167,7 @@ class _CalculateCellState extends State { mainAxisAlignment: MainAxisAlignment.end, children: [ FlowyText( + lineHeight: 1.0, widget.calculation!.calculationType.shortLabel .toUpperCase(), color: Theme.of(context).hintColor, @@ -175,6 +176,7 @@ class _CalculateCellState extends State { if (widget.calculation!.value.isNotEmpty) ...[ const HSpace(8), FlowyText( + lineHeight: 1.0, calculateValue, color: AFThemeExtension.of(context).textColor, overflow: TextOverflow.ellipsis, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart index eb1a76fe18..872c9bcf52 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart @@ -22,7 +22,11 @@ class CalculationTypeItem extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(type.label, overflow: TextOverflow.ellipsis), + text: FlowyText.medium( + type.label, + overflow: TextOverflow.ellipsis, + lineHeight: 1.0, + ), onTap: () { onTap(); PopoverContainer.of(context).close(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart index 2e080e8e68..85af9b3934 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart @@ -39,6 +39,7 @@ class ChoiceChipButton extends StatelessWidget { decoration: decoration, useIntrinsicWidth: true, text: FlowyText( + lineHeight: 1.0, filterInfo.fieldInfo.field.name, color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/condition_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/condition_button.dart index 736fdee63a..7ebe4e9f03 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/condition_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/condition_button.dart @@ -31,6 +31,7 @@ class ConditionButton extends StatelessWidget { child: FlowyButton( useIntrinsicWidth: true, text: FlowyText( + lineHeight: 1.0, conditionName, fontSize: 10, color: AFThemeExtension.of(context).textColor, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart index 6c5437ef51..d28cc5c263 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart @@ -163,6 +163,7 @@ class GridFilterPropertyCell extends StatelessWidget { return FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( + lineHeight: 1.0, fieldInfo.field.name, color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_menu.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_menu.dart index 52c361ab38..145c5aa1d9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_menu.dart @@ -87,6 +87,7 @@ class _AddFilterButtonState extends State { height: 28, child: FlowyButton( text: FlowyText( + lineHeight: 1.0, LocaleKeys.grid_settings_addFilter.tr(), color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart index 31ff0e8469..2179c56604 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart @@ -23,6 +23,7 @@ class GridAddRowButton extends StatelessWidget { ), ), text: FlowyText( + lineHeight: 1.0, LocaleKeys.grid_row_newRow.tr(), color: Theme.of(context).hintColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart index 56c48261f3..d127ef6f91 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart @@ -229,6 +229,7 @@ class FieldCellButton extends StatelessWidget { radius: radius, text: FlowyText.medium( field.name, + lineHeight: 1.0, maxLines: maxLines, overflow: TextOverflow.ellipsis, color: AFThemeExtension.of(context).textColor, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart index baa62657b8..bf0bf78e08 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart @@ -195,6 +195,7 @@ class _CreateFieldButtonState extends State { margin: GridSize.cellContentInsets, radius: BorderRadius.zero, text: FlowyText( + lineHeight: 1.0, LocaleKeys.grid_field_newProperty.tr(), overflow: TextOverflow.ellipsis, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart index abdeb90e47..f2c2540764 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart @@ -51,7 +51,11 @@ class RowActionMenu extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(action.text, overflow: TextOverflow.ellipsis), + text: FlowyText.medium( + action.text, + overflow: TextOverflow.ellipsis, + lineHeight: 1.0, + ), onTap: () { if (action == RowAction.delete) { NavigatorOkCancelDialog( diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart index 69e46a04ff..e85071a971 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart @@ -120,6 +120,7 @@ class GridSortPropertyCell extends StatelessWidget { hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( fieldInfo.name, + lineHeight: 1.0, color: AFThemeExtension.of(context).textColor, ), onTap: onTap, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/sort_choice_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/sort_choice_button.dart index a00bc1002f..4d509b3862 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/sort_choice_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/sort/sort_choice_button.dart @@ -34,6 +34,7 @@ class SortChoiceButton extends StatelessWidget { useIntrinsicWidth: true, text: FlowyText( text, + lineHeight: 1.0, color: AFThemeExtension.of(context).textColor, overflow: TextOverflow.ellipsis, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index 9c853414f1..ef7942ec55 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -222,6 +222,7 @@ class TabBarItemButton extends StatelessWidget { ), text: FlowyText( view.name, + lineHeight: 1.0, fontSize: FontSizes.s11, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart index ad1214fdba..8ff2ccea13 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart @@ -158,6 +158,7 @@ class _EditFieldButton extends StatelessWidget { child: FlowyButton( leftIcon: const FlowySvg(FlowySvgs.edit_s), text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.grid_field_editProperty.tr(), ), onTap: onTap, @@ -193,6 +194,7 @@ class FieldActionCell extends StatelessWidget { disable: !enable, text: FlowyText.medium( action.title(fieldInfo), + lineHeight: 1.0, color: enable ? null : Theme.of(context).disabledColor, ), onHover: (_) => popoverMutex?.close(), @@ -613,6 +615,7 @@ class _SwitchFieldButtonState extends State { }, text: FlowyText.medium( state.field.fieldType.i18n, + lineHeight: 1.0, color: isPrimary ? Theme.of(context).disabledColor : null, ), leftIcon: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_type_list.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_type_list.dart index 84d4c49177..69fe3635a8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_type_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_type_list.dart @@ -75,9 +75,7 @@ class FieldTypeCell extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium( - fieldType.i18n, - ), + text: FlowyText.medium(fieldType.i18n, lineHeight: 1.0), onTap: () => onSelectField(fieldType), leftIcon: FlowySvg( fieldType.svgData, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart index 0040444bb4..c04bcab92b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/date/date_time_format.dart @@ -23,7 +23,10 @@ class DateFormatButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr()), + text: FlowyText.medium( + LocaleKeys.grid_field_dateFormat.tr(), + lineHeight: 1.0, + ), onTap: onTap, onHover: onHover, rightIcon: const FlowySvg(FlowySvgs.more_s), @@ -47,7 +50,10 @@ class TimeFormatButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr()), + text: FlowyText.medium( + LocaleKeys.grid_field_timeFormat.tr(), + lineHeight: 1.0, + ), onTap: onTap, onHover: onHover, rightIcon: const FlowySvg(FlowySvgs.more_s), @@ -114,7 +120,10 @@ class DateFormatCell extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(dateFormat.title()), + text: FlowyText.medium( + dateFormat.title(), + lineHeight: 1.0, + ), rightIcon: checkmark, onTap: () => onSelected(dateFormat), ), @@ -199,7 +208,10 @@ class TimeFormatCell extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(timeFormat.title()), + text: FlowyText.medium( + timeFormat.title(), + lineHeight: 1.0, + ), rightIcon: checkmark, onTap: () => onSelected(timeFormat), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/number.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/number.dart index 244f38326c..feff29c59e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/number.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/number.dart @@ -32,6 +32,7 @@ class NumberTypeOptionEditorFactory implements TypeOptionEditorFactory { child: FlowyButton( rightIcon: const FlowySvg(FlowySvgs.more_s), text: FlowyText.medium( + lineHeight: 1.0, typeOption.format.title(), ), ), @@ -167,7 +168,10 @@ class NumberFormatCell extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(format.title()), + text: FlowyText.medium( + format.title(), + lineHeight: 1.0, + ), onTap: () => onSelected(format), rightIcon: checkmark, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/relation.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/relation.dart index 9ca2729cb6..dc1a6ef5c7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/relation.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/relation.dart @@ -61,6 +61,7 @@ class RelationTypeOptionEditorFactory implements TypeOptionEditorFactory { (meta) => meta.databaseId == typeOption.databaseId, ); return FlowyText( + lineHeight: 1.0, databaseMeta == null ? LocaleKeys .grid_relation_relatedDatabasePlaceholder @@ -134,6 +135,7 @@ class _DatabaseList extends StatelessWidget { child: FlowyButton( onTap: () => onSelectDatabase(meta.databaseId), text: FlowyText.medium( + lineHeight: 1.0, meta.databaseName, overflow: TextOverflow.ellipsis, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option.dart index 4c56121890..3c439a0f7a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option.dart @@ -181,6 +181,7 @@ class _AddOptionButton extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.grid_field_addSelectOption.tr(), ), onTap: () { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option_editor.dart index 5df44f4b49..9041b3bc60 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/select/select_option_editor.dart @@ -107,6 +107,7 @@ class _DeleteTag extends StatelessWidget { height: GridSize.popoverItemHeight, child: FlowyButton( text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.grid_selectOption_deleteTag.tr(), ), leftIcon: const FlowySvg(FlowySvgs.delete_s), @@ -230,6 +231,7 @@ class _SelectOptionColorCell extends StatelessWidget { child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( + lineHeight: 1.0, color.colorName(), color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/translate.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/translate.dart index 4cb5b0d9a3..f39a0d83c4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/translate.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/type_option_editor/translate.dart @@ -97,7 +97,12 @@ class SelectLanguageButton extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 30, - child: FlowyButton(text: FlowyText(language)), + child: FlowyButton( + text: FlowyText( + language, + lineHeight: 1.0, + ), + ), ); } } @@ -159,7 +164,10 @@ class LanguageCell extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.medium(languageTypeToLanguage(languageType)), + text: FlowyText.medium( + languageTypeToLanguage(languageType), + lineHeight: 1.0, + ), rightIcon: checkmark, onTap: () => onSelected(languageType), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart index ee021d59d3..af8b60b8db 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart @@ -193,6 +193,7 @@ class _GridGroupCell extends StatelessWidget { text: FlowyText.medium( name, color: AFThemeExtension.of(context).textColor, + lineHeight: 1.0, ), leftIcon: icon != null ? FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_action.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_action.dart index c9f4a796c0..c0cf547a06 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_action.dart @@ -50,7 +50,10 @@ class RowDetailPageDeleteButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.regular(LocaleKeys.grid_row_delete.tr()), + text: FlowyText.regular( + LocaleKeys.grid_row_delete.tr(), + lineHeight: 1.0, + ), leftIcon: const FlowySvg(FlowySvgs.trash_m), onTap: () { RowBackendService.deleteRows(viewId, [rowId]); @@ -76,7 +79,10 @@ class RowDetailPageDuplicateButton extends StatelessWidget { return SizedBox( height: GridSize.popoverItemHeight, child: FlowyButton( - text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()), + text: FlowyText.regular( + LocaleKeys.grid_row_duplicate.tr(), + lineHeight: 1.0, + ), leftIcon: const FlowySvg(FlowySvgs.copy_s), onTap: () { RowBackendService.duplicateRow(viewId, rowId); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart index 22dc9c3b47..1fd5b8822e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart @@ -220,6 +220,7 @@ class AddEmojiButton extends StatelessWidget { child: FlowyButton( useIntrinsicWidth: true, text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.document_plugins_cover_addIcon.tr(), ), leftIcon: const FlowySvg(FlowySvgs.emoji_s), @@ -242,6 +243,7 @@ class RemoveEmojiButton extends StatelessWidget { child: FlowyButton( useIntrinsicWidth: true, text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.document_plugins_cover_removeIcon.tr(), ), leftIcon: const FlowySvg(FlowySvgs.emoji_s), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart index 4a6aa78adb..fe08b53ab0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_property.dart @@ -294,7 +294,11 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget { return SizedBox( height: 30, child: FlowyButton( - text: FlowyText.medium(text, color: Theme.of(context).hintColor), + text: FlowyText.medium( + text, + lineHeight: 1.0, + color: Theme.of(context).hintColor, + ), hoverColor: AFThemeExtension.of(context).lightGreyHover, leftIcon: RotatedBox( quarterTurns: quarterTurns, @@ -381,6 +385,7 @@ class _CreateRowFieldButtonState extends State { child: FlowyButton( margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), text: FlowyText.medium( + lineHeight: 1.0, LocaleKeys.grid_field_newProperty.tr(), color: Theme.of(context).hintColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart index b8d1560141..1564559eba 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart @@ -80,6 +80,7 @@ class DatabaseViewLayoutCell extends StatelessWidget { child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( + lineHeight: 1.0, databaseLayout.layoutName, color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart index 4d01970406..252b702cd6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart @@ -82,6 +82,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( title(), + lineHeight: 1.0, color: AFThemeExtension.of(context).textColor, ), leftIcon: FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart index a72a30f2ea..f7dd93d9bc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_property_list.dart @@ -150,6 +150,7 @@ class _DatabasePropertyCellState extends State { child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium( + lineHeight: 1.0, widget.fieldInfo.name, color: AFThemeExtension.of(context).textColor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart index c56fbd09e9..b4447e1f01 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart @@ -53,7 +53,10 @@ class SelectableItem extends StatelessWidget { return SizedBox( height: 32, child: FlowyButton( - text: FlowyText.medium(item), + text: FlowyText.medium( + item, + lineHeight: 1.0, + ), rightIcon: isSelected ? const FlowySvg(FlowySvgs.check_s) : null, onTap: onTap, ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart index 7b45582d5b..b819bb9869 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart @@ -163,7 +163,6 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { fontSize: 16, maxLines: 2, textAlign: TextAlign.center, - lineHeight: 1.5, ), ], ], diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart index 560661d157..087d987262 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart @@ -247,6 +247,7 @@ class _CoverImagePreviewWidgetState extends State { size: Size(20, 20), ), text: FlowyText( + lineHeight: 1.0, LocaleKeys.document_plugins_cover_pickFromFiles.tr(), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart index 223db0e0e1..8f05e44185 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart @@ -161,7 +161,10 @@ class _ExportButton extends StatelessWidget { borderRadius: radius, ), radius: radius, - text: FlowyText(title), + text: FlowyText( + title, + lineHeight: 1.0, + ), leftIcon: FlowySvg(svg), onTap: onTap, ); diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index 87610b916b..7fdbab05b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -173,6 +173,7 @@ class _PublishedWidgetState extends State<_PublishedWidget> { ), radius: BorderRadius.circular(10), text: FlowyText.regular( + lineHeight: 1.0, LocaleKeys.shareAction_unPublish.tr(), textAlign: TextAlign.center, ), diff --git a/frontend/appflowy_flutter/lib/plugins/trash/trash_page.dart b/frontend/appflowy_flutter/lib/plugins/trash/trash_page.dart index b50f95342a..3de35d5537 100644 --- a/frontend/appflowy_flutter/lib/plugins/trash/trash_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/trash/trash_page.dart @@ -101,7 +101,10 @@ class _TrashPageState extends State { const Spacer(), IntrinsicWidth( child: FlowyButton( - text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr()), + text: FlowyText.medium( + LocaleKeys.trash_restoreAll.tr(), + lineHeight: 1.0, + ), leftIcon: const FlowySvg(FlowySvgs.restore_s), onTap: () { NavigatorAlertDialog( @@ -118,7 +121,10 @@ class _TrashPageState extends State { const HSpace(6), IntrinsicWidth( child: FlowyButton( - text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr()), + text: FlowyText.medium( + LocaleKeys.trash_deleteAll.tr(), + lineHeight: 1.0, + ), leftIcon: const FlowySvg(FlowySvgs.delete_s), onTap: () { NavigatorAlertDialog( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart index 8c64563eb3..6cb0e27547 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart @@ -257,6 +257,7 @@ class ViewMoreActionTypeWrapper extends CustomActionCell { textBuilder: (onHover) => FlowyText.regular( inner.name, fontSize: 14.0, + lineHeight: 1.0, figmaLineHeight: 18.0, color: inner == ViewMoreActionType.delete && onHover ? Theme.of(context).colorScheme.error diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart index c5e160fb03..a0b4ceacee 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart @@ -204,7 +204,6 @@ class _UpgradeToAILocalPlanState extends State<_UpgradeToAILocalPlan> { FlowyText.medium( LocaleKeys.sideBar_upgradeToAILocal.tr(), maxLines: 10, - lineHeight: 1.5, ), const VSpace(4), Opacity( @@ -213,7 +212,6 @@ class _UpgradeToAILocalPlanState extends State<_UpgradeToAILocalPlan> { LocaleKeys.sideBar_upgradeToAILocalDesc.tr(), fontSize: 12, maxLines: 10, - lineHeight: 1.5, ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart index dffad1a465..636a33a314 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart @@ -363,7 +363,6 @@ class _CurrentPathState extends State<_CurrentPath> { resetHoverOnRebuild: false, builder: (_, isHovering) => FlowyText.regular( widget.path, - lineHeight: 1.5, maxLines: 2, overflow: TextOverflow.ellipsis, decoration: isHovering ? TextDecoration.underline : null, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index f2dec9550c..3334450c73 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -626,7 +626,6 @@ class _Heading extends StatelessWidget { description!, fontSize: 12, maxLines: 5, - lineHeight: 1.5, ), ), ], diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 61a65e0c4b..1ffdd425d6 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -42,7 +42,7 @@ class FlowyText extends StatelessWidget { this.fontFamily, this.fallbackFontFamily, // https://api.flutter.dev/flutter/painting/TextStyle/height.html - this.lineHeight = 1, + this.lineHeight = 1.5, this.figmaLineHeight, this.withTooltip = false, this.isEmoji = false, @@ -61,7 +61,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight, + this.lineHeight = 1.5, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -82,7 +82,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight, + this.lineHeight = 1.5, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -102,7 +102,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1, + this.lineHeight = 1.5, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -122,7 +122,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1, + this.lineHeight = 1.5, this.withTooltip = false, this.isEmoji = false, this.strutStyle, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index c7a37bd290..6bedfe8cc4 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -1510,10 +1510,10 @@ packages: dependency: transitive description: name: pdf - sha256: "81d5522bddc1ef5c28e8f0ee40b71708761753c163e0c93a40df56fd515ea0f0" + sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" url: "https://pub.dev" source: hosted - version: "3.11.0" + version: "3.11.1" pdf_widget_wrapper: dependency: transitive description: @@ -1694,10 +1694,10 @@ packages: dependency: transitive description: name: qr - sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" realtime_client: dependency: transitive description: diff --git a/frontend/rust-lib/flowy-ai/src/ai_manager.rs b/frontend/rust-lib/flowy-ai/src/ai_manager.rs index ee510b36da..1ba15f754d 100644 --- a/frontend/rust-lib/flowy-ai/src/ai_manager.rs +++ b/frontend/rust-lib/flowy-ai/src/ai_manager.rs @@ -144,12 +144,19 @@ impl AIManager { chat_id: &str, message: &str, message_type: ChatMessageType, - text_stream_port: i64, + answer_stream_port: i64, + question_stream_port: i64, metadata: Vec, ) -> Result { let chat = self.get_or_create_chat_instance(chat_id).await?; let question = chat - .stream_chat_message(message, message_type, text_stream_port, metadata) + .stream_chat_message( + message, + message_type, + answer_stream_port, + question_stream_port, + metadata, + ) .await?; Ok(question) } diff --git a/frontend/rust-lib/flowy-ai/src/chat.rs b/frontend/rust-lib/flowy-ai/src/chat.rs index 1e8849828a..6305ac44da 100644 --- a/frontend/rust-lib/flowy-ai/src/chat.rs +++ b/frontend/rust-lib/flowy-ai/src/chat.rs @@ -5,6 +5,7 @@ use crate::entities::{ use crate::middleware::chat_service_mw::AICloudServiceMiddleware; use crate::notification::{make_notification, ChatNotification}; use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable}; +use crate::stream_message::StreamMessage; use allo_isolate::Isolate; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, MessageCursor, @@ -81,7 +82,8 @@ impl Chat { &self, message: &str, message_type: ChatMessageType, - text_stream_port: i64, + answer_stream_port: i64, + question_stream_port: i64, metadata: Vec, ) -> Result { if message.len() > 2000 { @@ -93,10 +95,19 @@ impl Chat { .store(false, std::sync::atomic::Ordering::SeqCst); self.stream_buffer.lock().await.clear(); - let stream_buffer = self.stream_buffer.clone(); + let mut question_sink = IsolateSink::new(Isolate::new(question_stream_port)); + let answer_stream_buffer = self.stream_buffer.clone(); let uid = self.user_service.user_id()?; let workspace_id = self.user_service.workspace_id()?; + let _ = question_sink + .send( + StreamMessage::Text { + text: message.to_string(), + } + .to_string(), + ) + .await; let question = self .chat_service .create_question( @@ -112,15 +123,31 @@ impl Chat { FlowyError::server_error() })?; - if self.chat_service.is_local_ai_enabled() { + let _ = question_sink + .send( + StreamMessage::MessageId { + message_id: question.message_id, + } + .to_string(), + ) + .await; + + if self.chat_service.is_local_ai_enabled() && !metadata.is_empty() { + let _ = question_sink + .send(StreamMessage::IndexStart.to_string()) + .await; if let Err(err) = self .chat_service - .index_message_metadata(&self.chat_id, &metadata) + .index_message_metadata(&self.chat_id, &metadata, &mut question_sink) .await { error!("Failed to index file: {}", err); } + let _ = question_sink + .send(StreamMessage::IndexEnd.to_string()) + .await; } + let _ = question_sink.send(StreamMessage::Done.to_string()).await; save_chat_message( self.user_service.sqlite_connection(uid)?, @@ -134,7 +161,7 @@ impl Chat { let cloud_service = self.chat_service.clone(); let user_service = self.user_service.clone(); tokio::spawn(async move { - let mut text_sink = IsolateSink::new(Isolate::new(text_stream_port)); + let mut answer_sink = IsolateSink::new(Isolate::new(answer_stream_port)); match cloud_service .stream_answer(&workspace_id, &chat_id, question_id) .await @@ -149,20 +176,20 @@ impl Chat { } match message { QuestionStreamValue::Answer { value } => { - stream_buffer.lock().await.push_str(&value); - let _ = text_sink.send(format!("data:{}", value)).await; + answer_stream_buffer.lock().await.push_str(&value); + let _ = answer_sink.send(format!("data:{}", value)).await; }, QuestionStreamValue::Metadata { value } => { if let Ok(s) = serde_json::to_string(&value) { - stream_buffer.lock().await.set_metadata(value); - let _ = text_sink.send(format!("metadata:{}", s)).await; + answer_stream_buffer.lock().await.set_metadata(value); + let _ = answer_sink.send(format!("metadata:{}", s)).await; } }, } }, Err(err) => { error!("[Chat] failed to stream answer: {}", err); - let _ = text_sink.send(format!("error:{}", err)).await; + let _ = answer_sink.send(format!("error:{}", err)).await; let pb = ChatMessageErrorPB { chat_id: chat_id.clone(), error_message: err.to_string(), @@ -178,9 +205,9 @@ impl Chat { Err(err) => { error!("[Chat] failed to stream answer: {}", err); if err.is_ai_response_limit_exceeded() { - let _ = text_sink.send("AI_RESPONSE_LIMIT".to_string()).await; + let _ = answer_sink.send("AI_RESPONSE_LIMIT".to_string()).await; } else { - let _ = text_sink.send(format!("error:{}", err)).await; + let _ = answer_sink.send(format!("error:{}", err)).await; } let pb = ChatMessageErrorPB { @@ -195,11 +222,11 @@ impl Chat { } make_notification(&chat_id, ChatNotification::FinishStreaming).send(); - if stream_buffer.lock().await.is_empty() { + if answer_stream_buffer.lock().await.is_empty() { return Ok(()); } - let content = stream_buffer.lock().await.take_content(); - let metadata = stream_buffer.lock().await.take_metadata(); + let content = answer_stream_buffer.lock().await.take_content(); + let metadata = answer_stream_buffer.lock().await.take_metadata(); let answer = cloud_service .create_answer(&workspace_id, &chat_id, &content, question_id, metadata) diff --git a/frontend/rust-lib/flowy-ai/src/entities.rs b/frontend/rust-lib/flowy-ai/src/entities.rs index 3693773177..007d7c1d06 100644 --- a/frontend/rust-lib/flowy-ai/src/entities.rs +++ b/frontend/rust-lib/flowy-ai/src/entities.rs @@ -61,9 +61,12 @@ pub struct StreamChatPayloadPB { pub message_type: ChatMessageTypePB, #[pb(index = 4)] - pub text_stream_port: i64, + pub answer_stream_port: i64, #[pb(index = 5)] + pub question_stream_port: i64, + + #[pb(index = 6)] pub metadata: Vec, } diff --git a/frontend/rust-lib/flowy-ai/src/event_handler.rs b/frontend/rust-lib/flowy-ai/src/event_handler.rs index b18a59504f..26251560b3 100644 --- a/frontend/rust-lib/flowy-ai/src/event_handler.rs +++ b/frontend/rust-lib/flowy-ai/src/event_handler.rs @@ -72,7 +72,8 @@ pub(crate) async fn stream_chat_message_handler( &data.chat_id, &data.message, message_type, - data.text_stream_port, + data.answer_stream_port, + data.question_stream_port, metadata, ) .await; diff --git a/frontend/rust-lib/flowy-ai/src/lib.rs b/frontend/rust-lib/flowy-ai/src/lib.rs index 9ebcbf75bb..be6c743d86 100644 --- a/frontend/rust-lib/flowy-ai/src/lib.rs +++ b/frontend/rust-lib/flowy-ai/src/lib.rs @@ -10,3 +10,4 @@ mod middleware; pub mod notification; mod persistence; mod protobuf; +mod stream_message; diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs index a3ba55b9a5..2fe6998a0a 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs @@ -16,11 +16,13 @@ use futures::Sink; use lib_infra::async_trait::async_trait; use std::collections::HashMap; +use crate::stream_message::StreamMessage; +use futures_util::SinkExt; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use serde_json::json; use std::ops::Deref; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::select; use tokio_stream::StreamExt; @@ -339,11 +341,11 @@ impl LocalAIController { .set_bool(APPFLOWY_LOCAL_AI_CHAT_RAG_ENABLED, enabled)?; Ok(enabled) } - pub async fn index_message_metadata( &self, chat_id: &str, metadata_list: &[ChatMessageMetadata], + index_process_sink: &mut (impl Sink + Unpin), ) -> FlowyResult<()> { for metadata in metadata_list { let mut index_metadata = HashMap::new(); @@ -351,52 +353,94 @@ impl LocalAIController { index_metadata.insert("at_name".to_string(), json!(format!("@{}", &metadata.name))); index_metadata.insert("source".to_string(), json!(&metadata.source)); - match &metadata.data.url { - None => match &metadata.data.content_type { - ChatMetadataContentType::Text | ChatMetadataContentType::Markdown => { - if metadata.data.validate() { - if let Err(err) = self - .index_file( - chat_id, - None, - Some(metadata.data.content.clone()), - Some(index_metadata), - ) - .await - { - error!("[AI Plugin] failed to index file: {:?}", err); - } - } - }, - _ => { - error!( - "[AI Plugin] unsupported content type: {:?}", - metadata.data.content_type - ); - }, - }, - Some(url) => { - let file_path = Path::new(url); - if file_path.exists() { - if let Err(err) = self - .index_file( - chat_id, - Some(file_path.to_path_buf()), - None, - Some(index_metadata), - ) - .await - { - error!("[AI Plugin] failed to index file: {:?}", err); - } - } - }, + if let Some(url) = &metadata.data.url { + let file_path = Path::new(url); + if file_path.exists() { + self + .process_index_file( + chat_id, + Some(file_path.to_path_buf()), + None, + metadata, + &index_metadata, + index_process_sink, + ) + .await?; + } + } else if matches!( + metadata.data.content_type, + ChatMetadataContentType::Text | ChatMetadataContentType::Markdown + ) && metadata.data.validate() + { + self + .process_index_file( + chat_id, + None, + Some(metadata.data.content.clone()), + metadata, + &index_metadata, + index_process_sink, + ) + .await?; + } else { + error!( + "[AI Plugin] unsupported content type: {:?}", + metadata.data.content_type + ); } } Ok(()) } + async fn process_index_file( + &self, + chat_id: &str, + file_path: Option, + content: Option, + metadata: &ChatMessageMetadata, + index_metadata: &HashMap, + index_process_sink: &mut (impl Sink + Unpin), + ) -> Result<(), FlowyError> { + let _ = index_process_sink + .send( + StreamMessage::StartIndexFile { + file_name: metadata.name.clone(), + } + .to_string(), + ) + .await; + + let result = self + .index_file(chat_id, file_path, content, Some(index_metadata.clone())) + .await; + match result { + Ok(_) => { + let _ = index_process_sink + .send( + StreamMessage::EndIndexFile { + file_name: metadata.name.clone(), + } + .to_string(), + ) + .await; + }, + Err(err) => { + let _ = index_process_sink + .send( + StreamMessage::IndexFileError { + file_name: metadata.name.clone(), + } + .to_string(), + ) + .await; + error!("[AI Plugin] failed to index file: {:?}", err); + }, + } + + Ok(()) + } + async fn enable_chat_plugin(&self, enabled: bool) -> FlowyResult<()> { info!("[AI Plugin] enable chat plugin: {}", enabled); if enabled { diff --git a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs index b52ef92810..dbab55610d 100644 --- a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs +++ b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs @@ -11,7 +11,7 @@ use flowy_ai_pub::cloud::{ RepeatedRelatedQuestion, StreamAnswer, StreamComplete, }; use flowy_error::{FlowyError, FlowyResult}; -use futures::{stream, StreamExt, TryStreamExt}; +use futures::{stream, Sink, StreamExt, TryStreamExt}; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; @@ -48,10 +48,11 @@ impl AICloudServiceMiddleware { &self, chat_id: &str, metadata_list: &[ChatMessageMetadata], + index_process_sink: &mut (impl Sink + Unpin), ) -> Result<(), FlowyError> { self .local_llm_controller - .index_message_metadata(chat_id, metadata_list) + .index_message_metadata(chat_id, metadata_list, index_process_sink) .await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-ai/src/stream_message.rs b/frontend/rust-lib/flowy-ai/src/stream_message.rs new file mode 100644 index 0000000000..d2b2b14100 --- /dev/null +++ b/frontend/rust-lib/flowy-ai/src/stream_message.rs @@ -0,0 +1,34 @@ +use std::fmt::Display; + +pub enum StreamMessage { + MessageId { message_id: i64 }, + IndexStart, + IndexEnd, + Text { text: String }, + Done, + StartIndexFile { file_name: String }, + EndIndexFile { file_name: String }, + IndexFileError { file_name: String }, +} +impl Display for StreamMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StreamMessage::MessageId { message_id } => write!(f, "message_id:{}", message_id), + StreamMessage::IndexStart => write!(f, "index_start:"), + StreamMessage::IndexEnd => write!(f, "index_end"), + StreamMessage::Text { text } => { + write!(f, "data:{}", text) + }, + StreamMessage::Done => write!(f, "done:"), + StreamMessage::StartIndexFile { file_name } => { + write!(f, "start_index_file:{}", file_name) + }, + StreamMessage::EndIndexFile { file_name } => { + write!(f, "end_index_file:{}", file_name) + }, + StreamMessage::IndexFileError { file_name } => { + write!(f, "index_file_error:{}", file_name) + }, + } + } +} From cd0f8d80e933ef857dd70d6033985b1fc0673ac2 Mon Sep 17 00:00:00 2001 From: Annie Date: Sat, 10 Aug 2024 18:05:53 +0800 Subject: [PATCH 02/28] chore: replace icons (#5914) Co-authored-by: nathan --- .../view/edit_database_view_screen.dart | 2 +- .../toolbar/calendar_layout_setting.dart | 2 +- .../database/widgets/database_layout_ext.dart | 2 +- .../setting/database_setting_action.dart | 2 +- .../lib/plugins/shared/share/_shared.dart | 3 +++ .../lib/style_widget/button.dart | 6 ++++- .../lib/widget/rounded_button.dart | 2 ++ frontend/resources/flowy_icons/16x/group.svg | 18 +++++++++++---- .../flowy_icons/24x/calendar_layout.svg | 23 +++++++++++++++---- .../flowy_icons/24x/database_layout.svg | 23 +++++++++---------- 10 files changed, 57 insertions(+), 26 deletions(-) diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart index 69ab500564..f5812541c8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/edit_database_view_screen.dart @@ -133,7 +133,7 @@ enum DatabaseViewSettings { filter => FlowySvgs.filter_s, sort => FlowySvgs.sort_ascending_s, board => FlowySvgs.board_s, - calendar => FlowySvgs.date_s, + calendar => FlowySvgs.calendar_s, duplicate => FlowySvgs.copy_s, delete => FlowySvgs.delete_s, }; diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart index 816f553704..9c7dd3bcd5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart @@ -181,7 +181,7 @@ class LayoutDateField extends StatelessWidget { onUpdated(fieldInfo.id); popoverMutex.close(); }, - leftIcon: const FlowySvg(FlowySvgs.grid_s), + leftIcon: const FlowySvg(FlowySvgs.date_s), rightIcon: fieldInfo.id == fieldId ? const FlowySvg(FlowySvgs.check_s) : null, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/database_layout_ext.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/database_layout_ext.dart index 4d79b2d075..f8118a7e51 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/database_layout_ext.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/database_layout_ext.dart @@ -26,7 +26,7 @@ extension DatabaseLayoutExtension on DatabaseLayoutPB { FlowySvgData get icon { return switch (this) { DatabaseLayoutPB.Board => FlowySvgs.board_s, - DatabaseLayoutPB.Calendar => FlowySvgs.date_s, + DatabaseLayoutPB.Calendar => FlowySvgs.calendar_s, DatabaseLayoutPB.Grid => FlowySvgs.grid_s, _ => throw UnimplementedError(), }; diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart index 252b702cd6..6b9fc5f90d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart @@ -23,7 +23,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { FlowySvgData iconData() { switch (this) { case DatabaseSettingAction.showProperties: - return FlowySvgs.properties_s; + return FlowySvgs.multiselect_s; case DatabaseSettingAction.showLayout: return FlowySvgs.database_layout_m; case DatabaseSettingAction.showGroup: diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart index feca33b2a7..99bb96c2fe 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart @@ -64,6 +64,9 @@ class _ShareButton extends StatelessWidget { Radius.circular(10.0), ), textColor: Theme.of(context).colorScheme.onPrimary, + onPressed: () { + // Do nothing, but it needs to provide an empty action in order to show cursorß + }, ); } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index f5b8ce505a..e5a9ce404f 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -292,6 +293,7 @@ class FlowyTextButton extends StatelessWidget { this.padding = const EdgeInsets.symmetric(horizontal: 8, vertical: 6), this.hoverColor, this.fillColor, + this.textColor, this.heading, this.radius, this.mainAxisAlignment = MainAxisAlignment.start, @@ -346,6 +348,7 @@ class FlowyTextButton extends StatelessWidget { final Widget? heading; final Color? hoverColor; final Color? fillColor; + final Color? textColor; final BorderRadius? radius; final MainAxisAlignment mainAxisAlignment; final String? tooltip; @@ -364,9 +367,10 @@ class FlowyTextButton extends StatelessWidget { children.add(heading!); children.add(const HSpace(8)); } - children.add(Text( + children.add(FlowyText( text, overflow: overflow, + color: textColor, textAlign: TextAlign.center, )); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart index 0fa181a1dd..a49b70c4d0 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/rounded_button.dart @@ -51,9 +51,11 @@ class RoundedTextButton extends StatelessWidget { radius: borderRadius ?? Corners.s6Border, fontColor: textColor ?? Theme.of(context).colorScheme.onPrimary, fillColor: fillColor ?? Theme.of(context).colorScheme.primary, + textColor: textColor, hoverColor: hoverColor ?? Theme.of(context).colorScheme.primaryContainer, padding: padding, + ), ), ); diff --git a/frontend/resources/flowy_icons/16x/group.svg b/frontend/resources/flowy_icons/16x/group.svg index f0a6dff4f9..eaa2b9c862 100644 --- a/frontend/resources/flowy_icons/16x/group.svg +++ b/frontend/resources/flowy_icons/16x/group.svg @@ -1,7 +1,15 @@ - - - - - + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/24x/calendar_layout.svg b/frontend/resources/flowy_icons/24x/calendar_layout.svg index 52e06e9111..eee5fca964 100644 --- a/frontend/resources/flowy_icons/24x/calendar_layout.svg +++ b/frontend/resources/flowy_icons/24x/calendar_layout.svg @@ -1,4 +1,19 @@ - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/24x/database_layout.svg b/frontend/resources/flowy_icons/24x/database_layout.svg index 240a5065d8..7b1fb9a846 100644 --- a/frontend/resources/flowy_icons/24x/database_layout.svg +++ b/frontend/resources/flowy_icons/24x/database_layout.svg @@ -1,12 +1,11 @@ - - - - - - \ No newline at end of file + + + + + + + + + + + From e2a923a796939d3a8491d70aceffeb17c7e2d0cc Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sat, 10 Aug 2024 20:49:06 +0800 Subject: [PATCH 03/28] chore: update editor version (#5918) * chore: update editor version * fix: new property field width on Mobile * feat: support enter to insert a new line on mobile * feat: optimzie callout style * feat: add hover effect on share button * chore: fix --------- Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> Co-authored-by: nathan --- .../message/ai_markdown_text.dart | 1 + .../grid/presentation/layout/sizes.dart | 1 + .../widgets/header/mobile_grid_header.dart | 2 +- .../presentation/editor_configuration.dart | 5 +- .../document/presentation/editor_page.dart | 3 + .../base/emoji_picker_button.dart | 22 +++--- .../callout/callout_block_component.dart | 70 +++++++++---------- .../copy_and_paste/custom_cut_command.dart | 2 +- .../copy_and_paste/custom_paste_command.dart | 2 +- .../copy_and_paste/paste_from_html.dart | 2 +- .../paste_from_in_app_json.dart | 2 +- .../copy_and_paste/paste_from_plain_text.dart | 2 +- .../presentation/editor_plugins/plugins.dart | 1 + .../quote/quote_block_shortcuts.dart | 39 +++++++++++ .../lib/plugins/shared/share/_shared.dart | 25 +++---- .../lib/style_widget/button.dart | 3 + frontend/appflowy_flutter/pubspec.lock | 8 +-- frontend/appflowy_flutter/pubspec.yaml | 4 +- 18 files changed, 122 insertions(+), 72 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart index ff7cb6bafe..e5359161b4 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart @@ -124,6 +124,7 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { editorScrollController: scrollController, blockComponentBuilders: blockBuilders, commandShortcutEvents: [customCopyCommand], + disableAutoScroll: true, editorState: editorState, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart index c028df7ced..2b1e558569 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart @@ -15,6 +15,7 @@ class GridSize { static double get popoverItemHeight => 26 * scale; static double get typeOptionSeparatorHeight => 4 * scale; static double get newPropertyButtonWidth => 140 * scale; + static double get mobileNewPropertyButtonWidth => 200 * scale; static EdgeInsets get cellContentInsets => EdgeInsets.symmetric( horizontal: GridSize.cellHPadding, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart index 90bfbbca13..8c7f931b40 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart @@ -197,7 +197,7 @@ class _CreateFieldButtonState extends State { Widget build(BuildContext context) { return Container( constraints: BoxConstraints( - maxWidth: GridSize.newPropertyButtonWidth, + maxWidth: GridSize.mobileNewPropertyButtonWidth, minHeight: GridSize.headerHeight, ), decoration: _getDecoration(context), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart index 8d8312073a..c4fa7e134c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -189,7 +189,10 @@ Map getEditorBuilderMap({ ), ), CalloutBlockKeys.type: CalloutBlockComponentBuilder( - configuration: configuration.copyWith(), + configuration: configuration.copyWith( + padding: (node) => const EdgeInsets.symmetric(vertical: 10), + ), + inlinePadding: const EdgeInsets.symmetric(vertical: 8.0), defaultColor: calloutBGColor, ), DividerBlockKeys.type: DividerBlockComponentBuilder( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 2fbd0a3986..87eec5b47c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -154,6 +154,9 @@ class _AppFlowyEditorPageState extends State { // callout block insertNewLineInCalloutBlock, + // quote block + insertNewLineInQuoteBlock, + // toggle list formatGreaterToToggleList, insertChildNodeInsideToggleList, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart index a487634a93..69e026f43f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart @@ -20,6 +20,7 @@ class EmojiPickerButton extends StatelessWidget { this.title, this.showBorder = true, this.enable = true, + this.margin, }); final String emoji; @@ -33,6 +34,7 @@ class EmojiPickerButton extends StatelessWidget { final String? title; final bool showBorder; final bool enable; + final EdgeInsets? margin; @override Widget build(BuildContext context) { @@ -44,6 +46,7 @@ class EmojiPickerButton extends StatelessWidget { height: emojiPickerSize.height, ), offset: offset, + margin: EdgeInsets.zero, direction: direction ?? PopoverDirection.rightWithTopAligned, popupBuilder: (_) => Container( width: emojiPickerSize.width, @@ -79,15 +82,16 @@ class EmojiPickerButton extends StatelessWidget { ); } - return FlowyTextButton( - emoji, - overflow: TextOverflow.visible, - fontSize: emojiSize, - padding: EdgeInsets.zero, - constraints: const BoxConstraints.tightFor(width: 36.0), - fillColor: Colors.transparent, - mainAxisAlignment: MainAxisAlignment.center, - onPressed: enable + return FlowyButton( + useIntrinsicWidth: true, + margin: + margin ?? const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + text: FlowyText.emoji( + emoji, + fontSize: emojiSize, + optimizeEmojiAlign: true, + ), + onTap: enable ? () async { final result = await context.push( Uri( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart index 3b1dc69920..d17f48e246 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart @@ -68,9 +68,11 @@ class CalloutBlockComponentBuilder extends BlockComponentBuilder { CalloutBlockComponentBuilder({ super.configuration, required this.defaultColor, + required this.inlinePadding, }); final Color defaultColor; + final EdgeInsets inlinePadding; @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { @@ -79,6 +81,7 @@ class CalloutBlockComponentBuilder extends BlockComponentBuilder { key: node.key, node: node, defaultColor: defaultColor, + inlinePadding: inlinePadding, configuration: configuration, showActions: showActions(node), actionBuilder: (context, state) => actionBuilder( @@ -105,9 +108,11 @@ class CalloutBlockComponentWidget extends BlockComponentStatefulWidget { super.actionBuilder, super.configuration = const BlockComponentConfiguration(), required this.defaultColor, + required this.inlinePadding, }); final Color defaultColor; + final EdgeInsets inlinePadding; @override State createState() => @@ -176,6 +181,7 @@ class _CalloutBlockComponentWidgetState borderRadius: const BorderRadius.all(Radius.circular(8.0)), color: backgroundColor, ), + padding: widget.inlinePadding, width: double.infinity, alignment: alignment, child: Row( @@ -183,27 +189,22 @@ class _CalloutBlockComponentWidgetState mainAxisSize: MainAxisSize.min, textDirection: textDirection, children: [ + if (PlatformExtension.isDesktopOrWeb) const HSpace(4.0), // the emoji picker button for the note - Padding( - padding: const EdgeInsets.only( - top: 6.0, - left: 4.0, - right: 4.0, - ), - child: EmojiPickerButton( - key: ValueKey( - emoji.toString(), - ), // force to refresh the popover state - enable: editorState.editable, - title: '', - emoji: emoji, - emojiSize: 16.0, - onSubmitted: (emoji, controller) { - setEmoji(emoji); - controller?.close(); - }, - ), + EmojiPickerButton( + key: ValueKey( + emoji.toString(), + ), // force to refresh the popover state + enable: editorState.editable, + title: '', + emoji: emoji, + emojiSize: 15.0, + onSubmitted: (emoji, controller) { + setEmoji(emoji); + controller?.close(); + }, ), + if (PlatformExtension.isDesktopOrWeb) const HSpace(4.0), Flexible( child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), @@ -248,24 +249,21 @@ class _CalloutBlockComponentWidgetState BuildContext context, TextDirection textDirection, ) { - return Padding( - padding: padding, - child: AppFlowyRichText( - key: forwardKey, - delegate: this, - node: widget.node, - editorState: editorState, - placeholderText: placeholderText, - textSpanDecorator: (textSpan) => textSpan.updateTextStyle( - textStyle, - ), - placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle( - placeholderTextStyle, - ), - textDirection: textDirection, - cursorColor: editorState.editorStyle.cursorColor, - selectionColor: editorState.editorStyle.selectionColor, + return AppFlowyRichText( + key: forwardKey, + delegate: this, + node: widget.node, + editorState: editorState, + placeholderText: placeholderText, + textSpanDecorator: (textSpan) => textSpan.updateTextStyle( + textStyle, ), + placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle( + placeholderTextStyle, + ), + textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart index e7f6fba181..db83e8e5f6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart @@ -1,6 +1,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; import 'package:flutter/material.dart'; /// cut. diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart index e81cf2f325..85b290863e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart @@ -7,7 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log, EditorCopyPaste; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart index e30bcf8a5d..2047e6dd47 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; extension PasteFromHtml on EditorState { Future pasteHtml(String html) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart index 4cc17d599b..8e425aeab6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log, EditorCopyPaste; extension PasteFromInAppJson on EditorState { Future pasteInAppJson(String inAppJson) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart index 114dde62a3..6100a0056d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart @@ -1,6 +1,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy/shared/patterns/common_patterns.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; extension PasteFromPlainText on EditorState { Future pastePlainText(String plainText) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index acbd3a1627..abc05d242c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -51,6 +51,7 @@ export 'openai/widgets/smart_edit_node_widget.dart'; export 'openai/widgets/smart_edit_toolbar_item.dart'; export 'outline/outline_block_component.dart'; export 'parsers/markdown_parsers.dart'; +export 'quote/quote_block_shortcuts.dart'; export 'slash_menu/slash_menu_items.dart'; export 'table/table_menu.dart'; export 'table/table_option_action.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart new file mode 100644 index 0000000000..ba3ad6e7df --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart @@ -0,0 +1,39 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/services.dart'; + +/// Pressing Enter in a quote block will insert a newline (\n) within the quote, +/// while pressing Shift+Enter in a quote will insert a new paragraph next to the quote. +/// +/// - support +/// - desktop +/// - mobile +/// - web +/// +final CharacterShortcutEvent insertNewLineInQuoteBlock = CharacterShortcutEvent( + key: 'insert a new line in quote block', + character: '\n', + handler: _insertNewLineHandler, +); + +CharacterShortcutEventHandler _insertNewLineHandler = (editorState) async { + final selection = editorState.selection?.normalized; + if (selection == null) { + return false; + } + + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null || node.type != QuoteBlockKeys.type) { + return false; + } + + // delete the selection + await editorState.deleteSelection(selection); + + if (HardwareKeyboard.instance.isShiftPressed) { + await editorState.insertNewLine(); + } else { + await editorState.insertTextAtCurrentSelection('\n'); + } + + return true; +}; diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart index 99bb96c2fe..3073833e17 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; @@ -7,7 +5,7 @@ import 'package:appflowy/plugins/shared/share/share_menu.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class ShareMenuButton extends StatelessWidget { @@ -55,18 +53,17 @@ class _ShareButton extends StatelessWidget { @override Widget build(BuildContext context) { - return RoundedTextButton( - title: LocaleKeys.shareAction_buttonText.tr(), - padding: const EdgeInsets.symmetric(horizontal: 14.0), - fontSize: 14.0, - fontWeight: FontWeight.w500, - borderRadius: const BorderRadius.all( - Radius.circular(10.0), + return FlowyButton( + text: FlowyText( + LocaleKeys.shareAction_buttonText.tr(), + fontSize: 14.0, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onPrimary, ), - textColor: Theme.of(context).colorScheme.onPrimary, - onPressed: () { - // Do nothing, but it needs to provide an empty action in order to show cursorß - }, + margin: const EdgeInsets.symmetric(horizontal: 14.0), + backgroundColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), + radius: BorderRadius.circular(10.0), ); } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index e5a9ce404f..fba47aa7d7 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -159,6 +159,7 @@ class FlowyButton extends StatelessWidget { final double iconPadding; final bool expand; final Color? borderColor; + final Color? backgroundColor; const FlowyButton({ super.key, @@ -183,6 +184,7 @@ class FlowyButton extends StatelessWidget { this.iconPadding = 6, this.expand = false, this.borderColor, + this.backgroundColor, }); @override @@ -211,6 +213,7 @@ class FlowyButton extends StatelessWidget { borderRadius: radius ?? Corners.s6Border, hoverColor: color, borderColor: borderColor ?? Colors.transparent, + backgroundColor: backgroundColor ?? Colors.transparent, ), onHover: disable ? null : onHover, isSelected: () => isSelected, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 6bedfe8cc4..7046e40f67 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: "0317779" - resolved-ref: "031777941ce53fe1aaa9a23177f6045b142e6e73" + ref: "3d6aff0" + resolved-ref: "3d6aff0d21de96411675417ab952f315cc949c7a" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "3.1.0" @@ -1198,10 +1198,10 @@ packages: dependency: "direct main" description: name: keyboard_height_plugin - sha256: bbb32804bf93601249c17c33125cd2e654f5ef650fc6acf1b031d69b478b35ce + sha256: "3a51c8ebb43465ebe0b3bad17f3b6d945421e58011f3f5a08134afe69a3d775f" url: "https://pub.dev" source: hosted - version: "0.0.5" + version: "0.1.5" leak_tracker: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 3a6064c9d6..dcb6004982 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -124,7 +124,7 @@ dependencies: image_gallery_saver: ^2.0.3 cached_network_image: ^3.3.0 leak_tracker: ^10.0.0 - keyboard_height_plugin: ^0.0.5 + keyboard_height_plugin: ^0.1.5 scrollable_positioned_list: ^0.3.8 flutter_cache_manager: ^3.3.1 share_plus: ^7.2.1 @@ -199,7 +199,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "0317779" + ref: "3d6aff0" appflowy_editor_plugins: git: From e87ade6b3f425ea2db03e9d56e256388da2a9858 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sat, 10 Aug 2024 21:06:54 +0800 Subject: [PATCH 04/28] chore: update tabbar icon (#5921) --- .../presentation/home/tabs/flowy_tab.dart | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart index 4760378f4b..11823300fb 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart @@ -3,8 +3,10 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; class FlowyTab extends StatefulWidget { @@ -26,22 +28,20 @@ class _FlowyTabState extends State { @override Widget build(BuildContext context) { - return GestureDetector( - onTertiaryTapUp: _closeTab, - child: MouseRegion( - onEnter: (_) => _setHovering(true), - onExit: (_) => _setHovering(), - child: Container( - width: HomeSizes.tabBarWidth, - height: HomeSizes.tabBarHeight, - decoration: BoxDecoration( - color: _getBackgroundColor(), - ), - child: ChangeNotifierProvider.value( - value: widget.pageManager.notifier, - child: Consumer( - builder: (context, value, child) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + return FlowyHover( + style: HoverStyle( + borderRadius: BorderRadius.zero, + backgroundColor: _getBackgroundColor(), + ), + builder: (context, onHover) { + return ChangeNotifierProvider.value( + value: widget.pageManager.notifier, + child: Consumer( + builder: (context, value, child) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: HomeSizes.tabBarWidth, + height: HomeSizes.tabBarHeight, child: Row( children: [ Expanded( @@ -49,15 +49,16 @@ class _FlowyTabState extends State { .tabBarWidget(widget.pageManager.plugin.id), ), Visibility( - visible: _isHovering, - child: FlowyIconButton( - onPressed: _closeTab, - hoverColor: Theme.of(context).hoverColor, - iconColorOnHover: - Theme.of(context).colorScheme.onSurface, - icon: const FlowySvg( - FlowySvgs.close_s, - size: Size.fromWidth(16), + visible: onHover, + child: SizedBox( + width: 26, + height: 26, + child: FlowyIconButton( + onPressed: _closeTab, + icon: const FlowySvg( + FlowySvgs.close_s, + size: Size.square(22), + ), ), ), ), @@ -66,8 +67,8 @@ class _FlowyTabState extends State { ), ), ), - ), - ), + ); + }, ); } From 2debd0283c63a195a93d3d15301affc173c4be3c Mon Sep 17 00:00:00 2001 From: Annie Date: Sun, 11 Aug 2024 15:02:29 +0800 Subject: [PATCH 05/28] chore: revamp question bubble (#5923) --- .../grid/presentation/widgets/row/action.dart | 2 +- .../tab_bar/desktop/tab_bar_header.dart | 5 + .../widgets/float_bubble/question_bubble.dart | 113 +++++------------- .../float_bubble/social_media_section.dart | 105 ++++++++++++++++ .../widgets/float_bubble/version_section.dart | 73 +++++++++++ .../presentation/widgets/pop_up_action.dart | 7 ++ frontend/resources/flowy_icons/16x/copy.svg | 4 +- frontend/resources/flowy_icons/16x/debug.svg | 20 ++++ .../resources/flowy_icons/16x/help_center.svg | 15 +-- .../resources/flowy_icons/16x/keyboard.svg | 14 +++ .../flowy_icons/16x/message_support.svg | 12 ++ .../flowy_icons/16x/share_feedback.svg | 12 ++ frontend/resources/flowy_icons/16x/star.svg | 10 ++ frontend/resources/translations/en.json | 9 +- 14 files changed, 297 insertions(+), 104 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart create mode 100644 frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart create mode 100644 frontend/resources/flowy_icons/16x/debug.svg create mode 100644 frontend/resources/flowy_icons/16x/keyboard.svg create mode 100644 frontend/resources/flowy_icons/16x/message_support.svg create mode 100644 frontend/resources/flowy_icons/16x/share_feedback.svg create mode 100644 frontend/resources/flowy_icons/16x/star.svg diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart index f2c2540764..71b20161b1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/action.dart @@ -86,7 +86,7 @@ enum RowAction { return switch (this) { insertAbove => FlowySvgs.arrow_s, insertBelow => FlowySvgs.add_s, - duplicate => FlowySvgs.copy_s, + duplicate => FlowySvgs.duplicate_s, delete => FlowySvgs.delete_s, }; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index ef7942ec55..6fc3b6d85d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -291,4 +291,9 @@ enum TabBarViewAction implements ActionCell { @override Widget? rightIcon(Color iconColor) => null; + + @override + Color? textColor(BuildContext context) { + return null; + } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 0f7a5d0623..d666e606f6 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -1,24 +1,18 @@ import 'package:appflowy/core/helpers/url_launcher.dart'; -import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/startup/tasks/rust_sdk.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/float_bubble/social_media_section.dart'; +import 'package:appflowy/workspace/presentation/widgets/float_bubble/version_section.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:styled_widget/styled_widget.dart'; class QuestionBubble extends StatelessWidget { const QuestionBubble({super.key}); @@ -26,7 +20,7 @@ class QuestionBubble extends StatelessWidget { @override Widget build(BuildContext context) { return const SizedBox.square( - dimension: 36.0, + dimension: 32.0, child: BubbleActionList(), ); } @@ -62,7 +56,9 @@ class _BubbleActionListState extends State { actions.addAll( BubbleAction.values.map((action) => BubbleActionWrapper(action)), ); - actions.add(FlowyVersionDescription()); + + actions.add(SocialMediaSection()); + actions.add(FlowyVersionSection()); final (color, borderColor, shadowColor, iconColor) = Theme.of(context).isLightMode @@ -83,6 +79,11 @@ class _BubbleActionListState extends State { direction: PopoverDirection.topWithRightAligned, actions: actions, offset: const Offset(0, -8), + constraints: const BoxConstraints( + minWidth: 200, + maxWidth: 460, + maxHeight: 400, + ), buildChild: (controller) { return FlowyTooltip( message: LocaleKeys.questionBubble_help.tr(), @@ -90,7 +91,7 @@ class _BubbleActionListState extends State { cursor: SystemMouseCursors.click, child: GestureDetector( child: Container( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(8.0), decoration: ShapeDecoration( color: color, shape: RoundedRectangleBorder( @@ -178,75 +179,21 @@ class _DebugToast { } } -class FlowyVersionDescription extends CustomActionCell { - @override - Widget buildWithContext(BuildContext context, PopoverController controller) { - return FutureBuilder( - future: PackageInfo.fromPlatform(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - return FlowyText( - "Error: ${snapshot.error}", - color: Theme.of(context).disabledColor, - ); - } - - final PackageInfo packageInfo = snapshot.data; - final String appName = packageInfo.appName; - final String version = packageInfo.version; - - return SizedBox( - height: 30, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Divider( - height: 1, - color: Theme.of(context).dividerColor, - thickness: 1.0, - ), - const VSpace(6), - GestureDetector( - behavior: HitTestBehavior.opaque, - onDoubleTap: () { - if (Env.internalBuild != '1' && !kDebugMode) { - return; - } - enableDocumentInternalLog = !enableDocumentInternalLog; - showToastNotification( - context, - message: enableDocumentInternalLog - ? 'Enabled Internal Log' - : 'Disabled Internal Log', - ); - }, - child: FlowyText( - '$appName $version', - color: Theme.of(context).hintColor, - ), - ), - ], - ).padding( - horizontal: ActionListSizes.itemHPadding, - ), - ); - } else { - return const SizedBox(height: 30); - } - }, - ); - } +enum BubbleAction { + whatsNews, + help, + debug, + shortcuts, + markdown, + github, } -enum BubbleAction { whatsNews, help, debug, shortcuts, markdown, github } - class BubbleActionWrapper extends ActionCell { BubbleActionWrapper(this.inner); final BubbleAction inner; @override - Widget? leftIcon(Color iconColor) => inner.emoji; + Widget? leftIcon(Color iconColor) => inner.icons; @override String get name => inner.name; @@ -270,26 +217,20 @@ extension QuestionBubbleExtension on BubbleAction { } } - Widget get emoji { + Widget? get icons { switch (this) { case BubbleAction.whatsNews: - return const FlowyText.regular('🆕'); + return const FlowySvg(FlowySvgs.star_s); case BubbleAction.help: - return const FlowyText.regular('👥'); + return const FlowySvg(FlowySvgs.message_support_s); case BubbleAction.debug: - return const FlowyText.regular('🐛'); + return const FlowySvg(FlowySvgs.debug_s); case BubbleAction.shortcuts: - return const FlowyText.regular('📋'); + return const FlowySvg(FlowySvgs.keyboard_s); case BubbleAction.markdown: - return const FlowyText.regular('✨'); + return const FlowySvg(FlowySvgs.number_s); case BubbleAction.github: - return const Padding( - padding: EdgeInsets.all(3.0), - child: FlowySvg( - FlowySvgs.archive_m, - size: Size.square(12), - ), - ); + return const FlowySvg(FlowySvgs.share_feedback_s); } } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart new file mode 100644 index 0000000000..dd7c17e7ac --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart @@ -0,0 +1,105 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flutter/material.dart'; + +class SocialMediaSection extends CustomActionCell { + @override + Widget buildWithContext(BuildContext context, PopoverController controller) { + final List children = [ + Divider( + height: 1, + color: Theme.of(context).dividerColor, + thickness: 1.0, + ), + ]; + + children.addAll( + SocialMedia.values.map( + (social) { + return ActionCellWidget( + action: SocialMediaWrapper(social), + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + switch (action.inner) { + case SocialMedia.reddit: + afLaunchUrlString( + 'https://www.reddit.com/r/AppFlowy/', + ); + case SocialMedia.twitter: + afLaunchUrlString( + 'https://x.com/appflowy', + ); + case SocialMedia.forum: + afLaunchUrlString('https://forum.appflowy.io/'); + } + }, + ); + }, + ), + ); + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Column( + children: children, + ), + ); + } +} + +enum SocialMedia { forum, twitter, reddit } + +class SocialMediaWrapper extends ActionCell { + SocialMediaWrapper(this.inner); + + final SocialMedia inner; + @override + Widget? leftIcon(Color iconColor) => inner.icons; + + @override + String get name => inner.name; + + @override + Color? textColor(BuildContext context) => inner.textColor(context); +} + +extension QuestionBubbleExtension on SocialMedia { + Color? textColor(BuildContext context) { + switch (this) { + case SocialMedia.reddit: + return Theme.of(context).hintColor; + + case SocialMedia.twitter: + return Theme.of(context).hintColor; + + case SocialMedia.forum: + return Theme.of(context).hintColor; + + default: + return null; + } + } + + String get name { + switch (this) { + case SocialMedia.forum: + return "Community Forum"; + case SocialMedia.twitter: + return "Twitter - @appflowy"; + case SocialMedia.reddit: + return "Reddit - r/appflowy"; + } + } + + Widget? get icons { + switch (this) { + case SocialMedia.reddit: + return null; + case SocialMedia.twitter: + return null; + case SocialMedia.forum: + return null; + } + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart new file mode 100644 index 0000000000..6363ee08e5 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart @@ -0,0 +1,73 @@ +import 'package:appflowy/env/env.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class FlowyVersionSection extends CustomActionCell { + @override + Widget buildWithContext(BuildContext context, PopoverController controller) { + return FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + return FlowyText( + "Error: ${snapshot.error}", + color: Theme.of(context).disabledColor, + ); + } + + final PackageInfo packageInfo = snapshot.data; + final String appName = packageInfo.appName; + final String version = packageInfo.version; + + return SizedBox( + height: 30, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Divider( + height: 1, + color: Theme.of(context).dividerColor, + thickness: 1.0, + ), + const VSpace(6), + GestureDetector( + behavior: HitTestBehavior.opaque, + onDoubleTap: () { + if (Env.internalBuild != '1' && !kDebugMode) { + return; + } + enableDocumentInternalLog = !enableDocumentInternalLog; + showToastNotification( + context, + message: enableDocumentInternalLog + ? 'Enabled Internal Log' + : 'Disabled Internal Log', + ); + }, + child: FlowyText( + '$appName $version', + color: Theme.of(context).hintColor, + fontSize: 12, + ).padding( + horizontal: ActionListSizes.itemHPadding, + ), + ), + ], + ), + ); + } else { + return const SizedBox(height: 30); + } + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart index 17f78cb8cf..5ce8ee88df 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/pop_up_action.dart @@ -103,6 +103,9 @@ abstract class ActionCell extends PopoverAction { Widget? leftIcon(Color iconColor) => null; Widget? rightIcon(Color iconColor) => null; String get name; + Color? textColor(BuildContext context) { + return null; + } } typedef PopoverActionCellBuilder = Widget Function( @@ -158,6 +161,7 @@ class ActionCellWidget extends StatelessWidget { leftIcon: leftIcon, rightIcon: rightIcon, name: actionCell.name, + textColor: actionCell.textColor(context), onTap: () => onSelected(action), ); } @@ -221,6 +225,7 @@ class HoverButton extends StatelessWidget { this.leftIcon, required this.name, this.rightIcon, + this.textColor, }); final VoidCallback onTap; @@ -228,6 +233,7 @@ class HoverButton extends StatelessWidget { final Widget? leftIcon; final Widget? rightIcon; final String name; + final Color? textColor; @override Widget build(BuildContext context) { @@ -248,6 +254,7 @@ class HoverButton extends StatelessWidget { name, overflow: TextOverflow.visible, lineHeight: 1.15, + color: textColor, ), ), if (rightIcon != null) ...[ diff --git a/frontend/resources/flowy_icons/16x/copy.svg b/frontend/resources/flowy_icons/16x/copy.svg index f11048fd2f..8830822589 100644 --- a/frontend/resources/flowy_icons/16x/copy.svg +++ b/frontend/resources/flowy_icons/16x/copy.svg @@ -1,4 +1,4 @@ - - + + diff --git a/frontend/resources/flowy_icons/16x/debug.svg b/frontend/resources/flowy_icons/16x/debug.svg new file mode 100644 index 0000000000..643edfbd23 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/debug.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/help_center.svg b/frontend/resources/flowy_icons/16x/help_center.svg index 52766922e9..7840906683 100644 --- a/frontend/resources/flowy_icons/16x/help_center.svg +++ b/frontend/resources/flowy_icons/16x/help_center.svg @@ -1,11 +1,6 @@ - - - - - - - - - - + + + + + diff --git a/frontend/resources/flowy_icons/16x/keyboard.svg b/frontend/resources/flowy_icons/16x/keyboard.svg new file mode 100644 index 0000000000..40cb2d42a8 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/keyboard.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/message_support.svg b/frontend/resources/flowy_icons/16x/message_support.svg new file mode 100644 index 0000000000..03e72384d7 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/message_support.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/share_feedback.svg b/frontend/resources/flowy_icons/16x/share_feedback.svg new file mode 100644 index 0000000000..9dc9b7f404 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/share_feedback.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/star.svg b/frontend/resources/flowy_icons/16x/star.svg new file mode 100644 index 0000000000..f4d6f7ae3c --- /dev/null +++ b/frontend/resources/flowy_icons/16x/star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 3503b7318f..d0e8a41b89 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1498,7 +1498,6 @@ "outline": "Outline", "codeBlock": "Code Block" }, - "plugins": { "referencedBoard": "Referenced Board", "referencedGrid": "Referenced Grid", @@ -1650,12 +1649,12 @@ "file": { "name": "File", "uploadTab": "Upload", - "networkTab": "Integrate link", + "networkTab": "Embed link", "placeholderText": "Click or drag and drop to upload a file", "placeholderDragging": "Drop the file to upload", "dropFileToUpload": "Drop the file to upload", "fileUploadHint": "Drag and drop a file here\nor click to select a file.", - "networkHint": "Enter a link to a file", + "networkHint": "Paste a file link", "networkUrlInvalid": "Invalid URL, please correct the URL and try again", "networkAction": "Embed file link", "fileTooBigError": "File size is too big, please upload a file with size less than 10MB", @@ -1743,7 +1742,7 @@ "placeholder": "Select language", "auto": "Auto" }, - "copyTooltip": "Copy contents of the code block", + "copyTooltip": "Copy", "searchLanguageHint": "Search for a language", "codeCopiedSnackbar": "Code copied to clipboard!" }, @@ -2411,4 +2410,4 @@ "commentAddedSuccessfully": "Comment added successfully.", "commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?" } -} +} \ No newline at end of file From 42d1bb84c544eb4aaa084ddefeca0f8c52048879 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 11 Aug 2024 15:10:30 +0800 Subject: [PATCH 06/28] chore: fix compile --- .../lib/workspace/presentation/home/tabs/flowy_tab.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart index 11823300fb..52007a3df7 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart @@ -6,7 +6,6 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; class FlowyTab extends StatefulWidget { @@ -24,7 +23,6 @@ class FlowyTab extends StatefulWidget { } class _FlowyTabState extends State { - bool _isHovering = false; @override Widget build(BuildContext context) { @@ -72,12 +70,6 @@ class _FlowyTabState extends State { ); } - void _setHovering([bool isHovering = false]) { - if (mounted) { - setState(() => _isHovering = isHovering); - } - } - Color _getBackgroundColor() { if (widget.isCurrent) { return Theme.of(context).colorScheme.onSecondaryContainer; From 510752868d77b426cd3e83ca95af90ba0c06f940 Mon Sep 17 00:00:00 2001 From: nathan Date: Sun, 11 Aug 2024 15:16:02 +0800 Subject: [PATCH 07/28] chore: fix flutter wanrings --- .../presentation/home/tabs/flowy_tab.dart | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart index 52007a3df7..a871d91565 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/tabs/flowy_tab.dart @@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; @@ -23,13 +22,12 @@ class FlowyTab extends StatefulWidget { } class _FlowyTabState extends State { - @override Widget build(BuildContext context) { return FlowyHover( - style: HoverStyle( + isSelected: () => widget.isCurrent, + style: const HoverStyle( borderRadius: BorderRadius.zero, - backgroundColor: _getBackgroundColor(), ), builder: (context, onHover) { return ChangeNotifierProvider.value( @@ -70,18 +68,6 @@ class _FlowyTabState extends State { ); } - Color _getBackgroundColor() { - if (widget.isCurrent) { - return Theme.of(context).colorScheme.onSecondaryContainer; - } - - if (_isHovering) { - return AFThemeExtension.of(context).lightGreyHover; - } - - return Theme.of(context).colorScheme.surfaceContainerHighest; - } - void _closeTab([TapUpDetails? details]) => context .read() .add(TabsEvent.closeTab(widget.pageManager.plugin.id)); From d33af70a5cf01cab0a97c7149b1a39b6d6b5be79 Mon Sep 17 00:00:00 2001 From: Annie Date: Sun, 11 Aug 2024 20:18:23 +0800 Subject: [PATCH 08/28] chore: revamp editor icon (#5925) * chore: revamp file block icon * chore: revamp other blocks icon * fix: flutter analyze --------- Co-authored-by: Lucas.Xu --- .../file/file_block_component.dart | 5 ++++- .../image/image_placeholder.dart | 19 +++++++++-------- .../multi_image_placeholder.dart | 21 +++++++++++-------- .../math_equation_block_component.dart | 7 ++++--- .../float_bubble/social_media_section.dart | 4 ++-- .../resources/flowy_icons/16x/file_upload.svg | 12 +++++++++++ frontend/resources/translations/en.json | 6 +++--- 7 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 frontend/resources/flowy_icons/16x/file_upload.svg diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart index 140506fc48..dc02b68503 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart @@ -208,7 +208,9 @@ class FileBlockComponentState extends State child: Row( children: [ const HSpace(10), - const Icon(Icons.upload_file_outlined), + FlowySvg(FlowySvgs.slash_menu_icon_file_s, + color: Theme.of(context).hintColor, + size: const Size.square(24),), const HSpace(10), ..._buildTrailing(context), ], @@ -348,6 +350,7 @@ class FileBlockComponentState extends State ? LocaleKeys.document_plugins_file_placeholderDragging.tr() : LocaleKeys.document_plugins_file_placeholderText.tr(), overflow: TextOverflow.ellipsis, + color: Theme.of(context).hintColor, ), ), ]; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart index 27b4f2f992..6e3941a584 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart @@ -71,9 +71,10 @@ class ImagePlaceholderState extends State { child: Row( children: [ const HSpace(10), - const FlowySvg( - FlowySvgs.image_placeholder_s, - size: Size.square(24), + FlowySvg( + FlowySvgs.slash_menu_icon_image_s, + size: const Size.square(24), + color: Theme.of(context).hintColor, ), const HSpace(10), ..._buildTrailing(context), @@ -187,12 +188,12 @@ class ImagePlaceholderState extends State { return [ Flexible( child: FlowyText( - PlatformExtension.isDesktop - ? isDraggingFiles - ? LocaleKeys.document_plugins_image_dropImageToInsert.tr() - : LocaleKeys.document_plugins_image_addAnImageDesktop.tr() - : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), - ), + PlatformExtension.isDesktop + ? isDraggingFiles + ? LocaleKeys.document_plugins_image_dropImageToInsert.tr() + : LocaleKeys.document_plugins_image_addAnImageDesktop.tr() + : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), + color: Theme.of(context).hintColor,), ), ]; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart index cdad9fe039..ba4c779928 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_service.dart'; @@ -66,17 +67,19 @@ class MultiImagePlaceholderState extends State { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), child: Row( children: [ - const Icon(Icons.photo_library_outlined, size: 24), + FlowySvg(FlowySvgs.slash_menu_icon_photo_gallery_s, + color: Theme.of(context).hintColor, + size: const Size.square(24),), const HSpace(10), FlowyText( - PlatformExtension.isDesktop - ? isDraggingFiles - ? LocaleKeys.document_plugins_image_dropImageToInsert - .tr() - : LocaleKeys.document_plugins_image_addAnImageDesktop - .tr() - : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), - ), + PlatformExtension.isDesktop + ? isDraggingFiles + ? LocaleKeys.document_plugins_image_dropImageToInsert + .tr() + : LocaleKeys.document_plugins_image_addAnImageDesktop + .tr() + : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), + color: Theme.of(context).hintColor,), ], ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart index 8fe381ca8e..b5631735e5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart @@ -173,11 +173,12 @@ class MathEquationBlockComponentWidgetState child: Row( children: [ const HSpace(10), - const Icon(Icons.text_fields_outlined), + FlowySvg(FlowySvgs.slash_menu_icon_math_equation_s, + color: Theme.of(context).hintColor, size: const Size.square(24),), const HSpace(10), FlowyText( - LocaleKeys.document_plugins_mathEquation_addMathEquation.tr(), - ), + LocaleKeys.document_plugins_mathEquation_addMathEquation.tr(), + color: Theme.of(context).hintColor,), ], ), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart index dd7c17e7ac..d5b70cd233 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart @@ -86,9 +86,9 @@ extension QuestionBubbleExtension on SocialMedia { case SocialMedia.forum: return "Community Forum"; case SocialMedia.twitter: - return "Twitter - @appflowy"; + return "Twitter – @appflowy"; case SocialMedia.reddit: - return "Reddit - r/appflowy"; + return "Reddit – r/appflowy"; } } diff --git a/frontend/resources/flowy_icons/16x/file_upload.svg b/frontend/resources/flowy_icons/16x/file_upload.svg new file mode 100644 index 0000000000..7df55f8984 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/file_upload.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index d0e8a41b89..1b1142caba 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1579,9 +1579,9 @@ "depth": "Depth" }, "image": { - "addAnImage": "Add an image", + "addAnImage": "Add images", "copiedToPasteBoard": "The image link has been copied to the clipboard", - "addAnImageDesktop": "Drop image(s) or click to add image(s)", + "addAnImageDesktop": "Add an image", "addAnImageMobile": "Click to add one or more images", "dropImageToInsert": "Drop images to insert", "imageUploadFailed": "Image upload failed", @@ -1650,7 +1650,7 @@ "name": "File", "uploadTab": "Upload", "networkTab": "Embed link", - "placeholderText": "Click or drag and drop to upload a file", + "placeholderText": "Upload or embed a file", "placeholderDragging": "Drop the file to upload", "dropFileToUpload": "Drop the file to upload", "fileUploadHint": "Drag and drop a file here\nor click to select a file.", From 3ff47b7e1e2b8a2603ce7840847d094aeabf4d1b Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Sun, 11 Aug 2024 20:18:35 +0800 Subject: [PATCH 09/28] fix: link to two calendars in same doc may failed randomly (#5926) * chore: udpate translation * fix: editor loses focus randomly when interacting with certain databases * fix: text align issues on Windows * chore: update editor version --- .../database/database_view_block_component.dart | 2 +- .../workspace/_sidebar_workspace_menu.dart | 3 --- .../flowy_infra_ui/lib/style_widget/text.dart | 15 +++++++++------ frontend/appflowy_flutter/pubspec.lock | 4 ++-- frontend/appflowy_flutter/pubspec.yaml | 2 +- frontend/resources/translations/en.json | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart index d780a1260e..f3292226aa 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart @@ -84,7 +84,7 @@ class _DatabaseBlockComponentWidgetState child: FocusScope( skipTraversal: true, onFocusChange: (value) { - if (value) { + if (value && keepEditorFocusNotifier.value == 0) { context.read().selection = null; } }, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart index e9dec98685..a383caa082 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; @@ -253,7 +251,6 @@ class _WorkspaceInfo extends StatelessWidget { overflow: TextOverflow.ellipsis, withTooltip: true, ), - if (Platform.isMacOS) const VSpace(2.0), // workspace members count FlowyText.regular( state.isLoading diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 1ffdd425d6..036de080b3 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -166,10 +166,6 @@ class FlowyText extends StatelessWidget { } } - if (isEmoji && (_useNotoColorEmoji || Platform.isWindows)) { - fontSize = fontSize * 0.8; - } - double? lineHeight; if (this.lineHeight != null) { lineHeight = this.lineHeight!; @@ -177,6 +173,14 @@ class FlowyText extends StatelessWidget { lineHeight = figmaLineHeight! / fontSize; } + if (isEmoji && (_useNotoColorEmoji || Platform.isWindows)) { + const scaleFactor = 0.9; + fontSize *= scaleFactor; + if (lineHeight != null) { + lineHeight /= scaleFactor; + } + } + final textStyle = Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: fontSize, fontWeight: fontWeight, @@ -206,8 +210,7 @@ class FlowyText extends StatelessWidget { textAlign: textAlign, overflow: overflow ?? TextOverflow.clip, style: textStyle, - strutStyle: ((Platform.isMacOS || Platform.isLinux) & !isEmoji) || - (isEmoji && optimizeEmojiAlign) + strutStyle: (isEmoji && optimizeEmojiAlign) ? StrutStyle.fromTextStyle( textStyle, forceStrutHeight: true, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 7046e40f67..21b0791d8d 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: "3d6aff0" - resolved-ref: "3d6aff0d21de96411675417ab952f315cc949c7a" + ref: dc10742 + resolved-ref: dc10742ba559e445e2ba1bd1b295cbf4758ccf3d url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "3.1.0" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index dcb6004982..0389146a42 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -199,7 +199,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "3d6aff0" + ref: "dc10742" appflowy_editor_plugins: git: diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 1b1142caba..b1a3ebe11a 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1472,7 +1472,7 @@ "numberedList": "Numbered List", "checkbox": "Checkbox", "doc": "Doc", - "linkedDoc": "Linked Doc", + "linkedDoc": "Link to page", "grid": "Grid", "linkedGrid": "Linked Grid", "kanban": "Kanban", From 23997e977c36f00963b24a0ebb605da02d2f1c4c Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Sun, 11 Aug 2024 20:39:25 +0800 Subject: [PATCH 10/28] refactor: revamp file upload and fix partitial upload bugs (#5924) * chore: upload chat file to local ai * chore: async func * chore: individual file progress * chore: fix test * chore: fix file upload --- .../application/chat_input_file_bloc.dart | 33 +-- .../presentation/chat_input/chat_input.dart | 9 +- .../chat_input/chat_input_attachment.dart | 2 +- frontend/appflowy_tauri/src-tauri/Cargo.lock | 26 +- frontend/appflowy_tauri/src-tauri/Cargo.toml | 2 +- .../appflowy_web_app/src-tauri/Cargo.lock | 26 +- .../appflowy_web_app/src-tauri/Cargo.toml | 2 +- frontend/rust-lib/Cargo.lock | 53 ++-- frontend/rust-lib/Cargo.toml | 5 +- .../build-tool/flowy-derive/Cargo.toml | 4 +- .../event-integration-test/src/lib.rs | 2 +- .../af_cloud_test/file_upload_test.rs | 112 +++---- frontend/rust-lib/flowy-ai-pub/src/cloud.rs | 8 +- frontend/rust-lib/flowy-ai/Cargo.toml | 3 +- frontend/rust-lib/flowy-ai/src/ai_manager.rs | 5 +- frontend/rust-lib/flowy-ai/src/chat.rs | 45 ++- .../rust-lib/flowy-ai/src/event_handler.rs | 1 - .../flowy-ai/src/local_ai/local_llm_chat.rs | 63 ++-- .../src/middleware/chat_service_mw.rs | 39 ++- .../rust-lib/flowy-ai/src/stream_message.rs | 9 +- .../flowy-core/src/deps_resolve/chat_deps.rs | 3 + .../flowy-core/src/integrate/trait_impls.rs | 35 +-- frontend/rust-lib/flowy-core/src/lib.rs | 1 + frontend/rust-lib/flowy-database2/Cargo.toml | 4 +- frontend/rust-lib/flowy-document/Cargo.toml | 2 +- .../flowy-document/src/event_handler.rs | 13 +- .../rust-lib/flowy-document/src/manager.rs | 5 +- .../flowy-document/tests/document/util.rs | 19 +- .../rust-lib/flowy-notification/Cargo.toml | 2 +- .../flowy-server/src/af_cloud/impls/chat.rs | 38 +-- .../rust-lib/flowy-server/src/default_impl.rs | 16 +- .../rust-lib/flowy-storage-pub/Cargo.toml | 1 + .../rust-lib/flowy-storage-pub/src/storage.rs | 89 ++++-- frontend/rust-lib/flowy-storage/Cargo.toml | 1 + .../rust-lib/flowy-storage/src/manager.rs | 279 ++++++++++-------- .../rust-lib/flowy-storage/src/uploader.rs | 30 +- 36 files changed, 551 insertions(+), 436 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_input_file_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_input_file_bloc.dart index 6f8877497f..048c8709b3 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_input_file_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_input_file_bloc.dart @@ -1,8 +1,5 @@ -import 'dart:async'; import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -10,40 +7,14 @@ part 'chat_input_file_bloc.freezed.dart'; class ChatInputFileBloc extends Bloc { ChatInputFileBloc({ + // ignore: avoid_unused_constructor_parameters required String chatId, required this.file, }) : super(const ChatInputFileState()) { on( (event, emit) async { await event.when( - initial: () async { - final payload = ChatFilePB( - filePath: file.filePath, - chatId: chatId, - ); - unawaited( - AIEventChatWithFile(payload).send().then((result) { - if (!isClosed) { - result.fold( - (_) { - add( - const ChatInputFileEvent.updateUploadState( - UploadFileIndicator.finish(), - ), - ); - }, - (err) { - add( - ChatInputFileEvent.updateUploadState( - UploadFileIndicator.error(err.toString()), - ), - ); - }, - ); - } - }), - ); - }, + initial: () async {}, updateUploadState: (UploadFileIndicator indicator) { emit(state.copyWith(uploadFileIndicator: indicator)); }, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart index 9f68586757..60de3fd528 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart @@ -154,15 +154,16 @@ class _ChatInputState extends State { children: [ // TODO(lucas): support mobile if (PlatformExtension.isDesktop && - widget.aiType == const AIType.localAI()) + widget.aiType.isLocalAI()) _attachmentButton(buttonPadding), // text field Expanded(child: _inputTextField(context, textPadding)), - // at button + // mention button // TODO(lucas): support mobile - if (PlatformExtension.isDesktop) _atButton(buttonPadding), + if (PlatformExtension.isDesktop) + _mentionButton(buttonPadding), // send button _sendButton(buttonPadding), @@ -352,7 +353,7 @@ class _ChatInputState extends State { ); } - Widget _atButton(EdgeInsets buttonPadding) { + Widget _mentionButton(EdgeInsets buttonPadding) { return Padding( padding: buttonPadding, child: SizedBox.square( diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_attachment.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_attachment.dart index b4eee5d8ef..954988da7c 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_attachment.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_attachment.dart @@ -17,7 +17,7 @@ class ChatInputAttachment extends StatelessWidget { message: LocaleKeys.chat_uploadFile.tr(), child: FlowyIconButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, - radius: BorderRadius.circular(18), + radius: BorderRadius.circular(6), icon: FlowySvg( FlowySvgs.ai_attachment_s, size: const Size.square(20), diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 692ea6eb43..20ede978d8 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -192,7 +192,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -826,7 +826,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "again", "anyhow", @@ -876,7 +876,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "collab-entity", "collab-rt-entity", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "futures-channel", "futures-util", @@ -1132,7 +1132,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "async-trait", @@ -1421,7 +1421,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa 1.0.6", - "phf 0.11.2", + "phf 0.8.0", "smallvec", ] @@ -1532,7 +1532,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -3051,7 +3051,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "futures-util", @@ -3068,7 +3068,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -3500,7 +3500,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -6098,7 +6098,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index b91db1138a..026dd6daf6 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -53,7 +53,7 @@ collab-user = { version = "0.2" } # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } [dependencies] serde_json.workspace = true diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock index ae08a44db4..e940bdd58a 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.lock +++ b/frontend/appflowy_web_app/src-tauri/Cargo.lock @@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "again", "anyhow", @@ -850,7 +850,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "collab-entity", "collab-rt-entity", @@ -862,7 +862,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "futures-channel", "futures-util", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -1140,7 +1140,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "async-trait", @@ -1411,7 +1411,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa 1.0.10", - "phf 0.11.2", + "phf 0.8.0", "smallvec", ] @@ -1522,7 +1522,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -3118,7 +3118,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "futures-util", @@ -3135,7 +3135,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -3572,7 +3572,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -6162,7 +6162,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml index 59cc7abffb..e2b61d6d39 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.toml +++ b/frontend/appflowy_web_app/src-tauri/Cargo.toml @@ -52,7 +52,7 @@ collab-user = { version = "0.2" } # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } [dependencies] serde_json.workspace = true diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index ae4b034439..9ff90f08e9 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -718,7 +718,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "again", "anyhow", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "collab-entity", "collab-rt-entity", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "futures-channel", "futures-util", @@ -855,7 +855,7 @@ dependencies = [ "collab", "collab-entity", "collab-plugins", - "dashmap", + "dashmap 5.5.3", "getrandom 0.2.10", "js-sys", "lazy_static", @@ -993,7 +993,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bincode", @@ -1018,7 +1018,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "async-trait", @@ -1347,6 +1347,20 @@ dependencies = [ "parking_lot_core 0.9.8", ] +[[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -1356,7 +1370,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -1783,7 +1797,7 @@ dependencies = [ "appflowy-plugin", "base64 0.21.5", "bytes", - "dashmap", + "dashmap 6.0.1", "dotenv", "flowy-ai-pub", "flowy-codegen", @@ -1791,6 +1805,7 @@ dependencies = [ "flowy-error", "flowy-notification", "flowy-sqlite", + "flowy-storage-pub", "futures", "futures-util", "lib-dispatch", @@ -1958,7 +1973,7 @@ dependencies = [ "collab-integrate", "collab-plugins", "csv", - "dashmap", + "dashmap 6.0.1", "event-integration-test", "fancy-regex 0.11.0", "flowy-codegen", @@ -2009,7 +2024,7 @@ dependencies = [ name = "flowy-derive" version = "0.1.0" dependencies = [ - "dashmap", + "dashmap 6.0.1", "flowy-ast", "flowy-codegen", "lazy_static", @@ -2031,7 +2046,7 @@ dependencies = [ "collab-entity", "collab-integrate", "collab-plugins", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-document-pub", @@ -2171,7 +2186,7 @@ name = "flowy-notification" version = "0.1.0" dependencies = [ "bytes", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "lazy_static", @@ -2320,6 +2335,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-error", @@ -2354,6 +2370,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tracing", ] [[package]] @@ -2730,7 +2747,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "futures-util", @@ -2747,7 +2764,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", @@ -3112,7 +3129,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "bytes", @@ -5321,7 +5338,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" dependencies = [ "anyhow", "app-error", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index a6aebe740b..54b2f1a187 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -93,14 +93,15 @@ yrs = "0.19.2" validator = { version = "0.16.1", features = ["derive"] } tokio-util = "0.7.11" zip = "2.1.3" +dashmap = "6.0.1" # Please using the following command to update the revision id # Current directory: frontend # Run the script.add_workspace_members: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } -client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } +client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } [profile.dev] opt-level = 0 diff --git a/frontend/rust-lib/build-tool/flowy-derive/Cargo.toml b/frontend/rust-lib/build-tool/flowy-derive/Cargo.toml index ce84acd0eb..763210c558 100644 --- a/frontend/rust-lib/build-tool/flowy-derive/Cargo.toml +++ b/frontend/rust-lib/build-tool/flowy-derive/Cargo.toml @@ -14,8 +14,8 @@ syn = { version = "1.0.109", features = ["extra-traits", "visit"] } quote = "1.0" proc-macro2 = "1.0" flowy-ast.workspace = true -lazy_static = {version = "1.4.0"} -dashmap = "5" +lazy_static = { version = "1.4.0" } +dashmap.workspace = true flowy-codegen.workspace = true serde_json.workspace = true walkdir = "2.3.2" diff --git a/frontend/rust-lib/event-integration-test/src/lib.rs b/frontend/rust-lib/event-integration-test/src/lib.rs index 69096bbd37..cd2a01d84f 100644 --- a/frontend/rust-lib/event-integration-test/src/lib.rs +++ b/frontend/rust-lib/event-integration-test/src/lib.rs @@ -91,7 +91,7 @@ impl EventIntegrationTest { Self::new_with_config(config).await } - pub fn set_no_cleanup(&mut self) { + pub fn skip_clean(&mut self) { self.cleaner.lock().should_clean = false; } diff --git a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs index ba9e2023fe..cf0050341e 100644 --- a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs @@ -1,39 +1,14 @@ use crate::document::generate_random_bytes; use event_integration_test::user_event::user_localhost_af_cloud; use event_integration_test::EventIntegrationTest; -use flowy_storage_pub::storage::UploadStatus; +use flowy_storage_pub::storage::FileUploadState; use std::env::temp_dir; +use std::sync::Arc; use std::time::Duration; use tokio::fs; use tokio::fs::File; use tokio::io::AsyncWriteExt; - -#[tokio::test] -async fn af_cloud_upload_file_test() { - user_localhost_af_cloud().await; - let test = EventIntegrationTest::new().await; - test.af_cloud_sign_up().await; - - let workspace_id = test.get_current_workspace().await.id; - let file_path = generate_file_with_bytes_len(1024).await.0; - let mut rx = test.storage_manager.subscribe_upload_result(); - - let created_upload = test - .storage_manager - .storage_service - .create_upload(&workspace_id, "temp_test", &file_path) - .await - .unwrap(); - - while let Ok(result) = rx.recv().await { - if result.file_id == created_upload.file_id && result.status == UploadStatus::Finish { - break; - } - } - - let _ = fs::remove_file(file_path).await; -} - +use tokio::sync::Mutex; #[tokio::test] async fn af_cloud_upload_big_file_test() { user_localhost_af_cloud().await; @@ -42,32 +17,39 @@ async fn af_cloud_upload_big_file_test() { tokio::time::sleep(Duration::from_secs(6)).await; let workspace_id = test.get_current_workspace().await.id; - let (file_path, upload_data) = generate_file_with_bytes_len(30 * 1024 * 1024).await; - let created_upload = test + let (file_path, upload_data) = generate_file_with_bytes_len(15 * 1024 * 1024).await; + let (created_upload, rx) = test .storage_manager .storage_service - .create_upload(&workspace_id, "temp_test", &file_path) + .create_upload(&workspace_id, "temp_test", &file_path, false) .await .unwrap(); - let mut rx = test.storage_manager.subscribe_upload_result(); - while let Ok(result) = rx.recv().await { - if result.file_id == created_upload.file_id && result.status == UploadStatus::InProgress { - break; + let mut rx = rx.unwrap(); + while let Some(state) = rx.recv().await { + if let FileUploadState::Uploading { progress } = state { + if progress > 0.1 { + break; + } } } // Simulate a restart let config = test.config.clone(); - test.set_no_cleanup(); + test.skip_clean(); drop(test); tokio::time::sleep(Duration::from_secs(3)).await; // Restart the test. It will load unfinished uploads let test = EventIntegrationTest::new_with_config(config).await; - let mut rx = test.storage_manager.subscribe_upload_result(); - while let Ok(result) = rx.recv().await { - if result.file_id == created_upload.file_id && result.status == UploadStatus::Finish { + let mut rx = test + .storage_manager + .subscribe_file_state(&created_upload.file_id) + .await + .unwrap(); + + while let Some(state) = rx.recv().await { + if let FileUploadState::Finished { .. } = state { break; } } @@ -90,35 +72,61 @@ async fn af_cloud_upload_6_files_test() { user_localhost_af_cloud().await; let test = EventIntegrationTest::new().await; test.af_cloud_sign_up().await; - let workspace_id = test.get_current_workspace().await.id; - let mut rx = test.storage_manager.subscribe_upload_result(); let mut created_uploads = vec![]; + let mut receivers = vec![]; for file_size in [1, 2, 5, 8, 12, 20] { let file_path = generate_file_with_bytes_len(file_size * 1024 * 1024) .await .0; - let created_upload = test + let (created_upload, rx) = test .storage_manager .storage_service - .create_upload(&workspace_id, "temp_test", &file_path) + .create_upload(&workspace_id, "temp_test", &file_path, false) .await .unwrap(); + receivers.push(rx.unwrap()); created_uploads.push(created_upload); - let _ = fs::remove_file(file_path).await; } - while let Ok(result) = rx.recv().await { - if result.status == UploadStatus::Finish { - created_uploads.retain(|upload| upload.file_id != result.file_id); - } - - if created_uploads.is_empty() { - break; - } + // Wait for all uploads to finish + let uploads = Arc::new(Mutex::new(created_uploads)); + let mut handles = vec![]; + for mut receiver in receivers { + let cloned_uploads = uploads.clone(); + let cloned_test = test.clone(); + let handle = tokio::spawn(async move { + if let Some(state) = cloned_test + .storage_manager + .get_file_state(&receiver.file_id) + .await + { + if let FileUploadState::Finished { file_id } = state { + cloned_uploads + .lock() + .await + .retain(|upload| upload.file_id != file_id); + } + } else { + while let Some(value) = receiver.recv().await { + if let FileUploadState::Finished { file_id } = value { + cloned_uploads + .lock() + .await + .retain(|upload| upload.file_id != file_id); + break; + } + } + } + }); + handles.push(handle); } + + // join all handles + futures::future::join_all(handles).await; + assert_eq!(uploads.lock().await.len(), 0); } async fn generate_file_with_bytes_len(len: usize) -> (String, Vec) { diff --git a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs index 5096c8b981..e29ddae174 100644 --- a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs @@ -35,14 +35,14 @@ pub trait ChatCloudService: Send + Sync + 'static { metadata: &[ChatMessageMetadata], ) -> Result; - fn create_answer( + async fn create_answer( &self, workspace_id: &str, chat_id: &str, message: &str, question_id: i64, metadata: Option, - ) -> FutureResult; + ) -> Result; async fn stream_answer( &self, @@ -58,13 +58,13 @@ pub trait ChatCloudService: Send + Sync + 'static { question_message_id: i64, ) -> Result; - fn get_chat_messages( + async fn get_chat_messages( &self, workspace_id: &str, chat_id: &str, offset: MessageCursor, limit: u64, - ) -> FutureResult; + ) -> Result; async fn get_related_message( &self, diff --git a/frontend/rust-lib/flowy-ai/Cargo.toml b/frontend/rust-lib/flowy-ai/Cargo.toml index 8d7db1609f..3e26f38a5e 100644 --- a/frontend/rust-lib/flowy-ai/Cargo.toml +++ b/frontend/rust-lib/flowy-ai/Cargo.toml @@ -22,7 +22,7 @@ bytes.workspace = true validator = { workspace = true, features = ["derive"] } lib-infra = { workspace = true, features = ["isolate_flutter"] } flowy-ai-pub.workspace = true -dashmap = "5.5" +dashmap.workspace = true flowy-sqlite = { workspace = true } tokio.workspace = true futures.workspace = true @@ -44,6 +44,7 @@ md5 = "0.7.0" zip = { workspace = true, features = ["deflate"] } zip-extensions = "0.8.0" pin-project = "1.1.5" +flowy-storage-pub = { workspace = true } [target.'cfg(any(target_os = "macos", target_os = "linux", target_os = "windows"))'.dependencies] notify = "6.1.1" diff --git a/frontend/rust-lib/flowy-ai/src/ai_manager.rs b/frontend/rust-lib/flowy-ai/src/ai_manager.rs index 1ba15f754d..a8b9f5fc8f 100644 --- a/frontend/rust-lib/flowy-ai/src/ai_manager.rs +++ b/frontend/rust-lib/flowy-ai/src/ai_manager.rs @@ -13,9 +13,10 @@ use flowy_error::{FlowyError, FlowyResult}; use flowy_sqlite::kv::KVStorePreferences; use flowy_sqlite::DBConnection; +use flowy_storage_pub::storage::StorageService; use lib_infra::util::timestamp; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use tracing::{info, trace}; pub trait AIUserService: Send + Sync + 'static { @@ -38,6 +39,7 @@ impl AIManager { chat_cloud_service: Arc, user_service: impl AIUserService, store_preferences: Arc, + storage_service: Weak, ) -> AIManager { let user_service = Arc::new(user_service); let plugin_manager = Arc::new(PluginManager::new()); @@ -53,6 +55,7 @@ impl AIManager { user_service.clone(), chat_cloud_service, local_ai_controller.clone(), + storage_service, )); Self { diff --git a/frontend/rust-lib/flowy-ai/src/chat.rs b/frontend/rust-lib/flowy-ai/src/chat.rs index 6305ac44da..b0fd1c42d3 100644 --- a/frontend/rust-lib/flowy-ai/src/chat.rs +++ b/frontend/rust-lib/flowy-ai/src/chat.rs @@ -89,6 +89,15 @@ impl Chat { if message.len() > 2000 { return Err(FlowyError::text_too_long().with_context("Exceeds maximum message 2000 length")); } + + trace!( + "[Chat] stream chat message: chat_id={}, message={}, message_type={:?}, metadata={:?}", + self.chat_id, + message, + message_type, + metadata + ); + // clear self .stop_stream @@ -101,12 +110,7 @@ impl Chat { let workspace_id = self.user_service.workspace_id()?; let _ = question_sink - .send( - StreamMessage::Text { - text: message.to_string(), - } - .to_string(), - ) + .send(StreamMessage::Text(message.to_string()).to_string()) .await; let question = self .chat_service @@ -124,31 +128,18 @@ impl Chat { })?; let _ = question_sink - .send( - StreamMessage::MessageId { - message_id: question.message_id, - } - .to_string(), - ) + .send(StreamMessage::MessageId(question.message_id).to_string()) .await; - - if self.chat_service.is_local_ai_enabled() && !metadata.is_empty() { - let _ = question_sink - .send(StreamMessage::IndexStart.to_string()) - .await; - if let Err(err) = self - .chat_service - .index_message_metadata(&self.chat_id, &metadata, &mut question_sink) - .await - { - error!("Failed to index file: {}", err); - } - let _ = question_sink - .send(StreamMessage::IndexEnd.to_string()) - .await; + if let Err(err) = self + .chat_service + .index_message_metadata(&self.chat_id, &metadata, &mut question_sink) + .await + { + error!("Failed to index file: {}", err); } let _ = question_sink.send(StreamMessage::Done.to_string()).await; + // Save message to disk save_chat_message( self.user_service.sqlite_connection(uid)?, &self.chat_id, diff --git a/frontend/rust-lib/flowy-ai/src/event_handler.rs b/frontend/rust-lib/flowy-ai/src/event_handler.rs index 26251560b3..21f14070f4 100644 --- a/frontend/rust-lib/flowy-ai/src/event_handler.rs +++ b/frontend/rust-lib/flowy-ai/src/event_handler.rs @@ -51,7 +51,6 @@ pub(crate) async fn stream_chat_message_handler( ChatMessageMetadata { data: ChatMetadataData { content: metadata.data, - url: None, content_type, size: content_len as i64, }, diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs index 2fe6998a0a..8ea17c1638 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs @@ -26,7 +26,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use tokio::select; use tokio_stream::StreamExt; -use tracing::{debug, error, info, instrument}; +use tracing::{debug, error, info, instrument, trace}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LLMSetting { @@ -348,45 +348,54 @@ impl LocalAIController { index_process_sink: &mut (impl Sink + Unpin), ) -> FlowyResult<()> { for metadata in metadata_list { + if let Err(err) = metadata.data.validate() { + error!( + "[AI Plugin] invalid metadata: {:?}, error: {:?}", + metadata, err + ); + continue; + } + let mut index_metadata = HashMap::new(); index_metadata.insert("name".to_string(), json!(&metadata.name)); index_metadata.insert("at_name".to_string(), json!(format!("@{}", &metadata.name))); index_metadata.insert("source".to_string(), json!(&metadata.source)); - - if let Some(url) = &metadata.data.url { - let file_path = Path::new(url); - if file_path.exists() { + match &metadata.data.content_type { + ChatMetadataContentType::Unknown => { + error!( + "[AI Plugin] unsupported content type: {:?}", + metadata.data.content_type + ); + }, + ChatMetadataContentType::Text | ChatMetadataContentType::Markdown => { + trace!("[AI Plugin]: index text: {}", metadata.data.content); self .process_index_file( chat_id, - Some(file_path.to_path_buf()), None, + Some(metadata.data.content.clone()), metadata, &index_metadata, index_process_sink, ) .await?; - } - } else if matches!( - metadata.data.content_type, - ChatMetadataContentType::Text | ChatMetadataContentType::Markdown - ) && metadata.data.validate() - { - self - .process_index_file( - chat_id, - None, - Some(metadata.data.content.clone()), - metadata, - &index_metadata, - index_process_sink, - ) - .await?; - } else { - error!( - "[AI Plugin] unsupported content type: {:?}", - metadata.data.content_type - ); + }, + ChatMetadataContentType::PDF => { + trace!("[AI Plugin]: index pdf file: {}", metadata.data.content); + let file_path = Path::new(&metadata.data.content); + if file_path.exists() { + self + .process_index_file( + chat_id, + Some(file_path.to_path_buf()), + None, + metadata, + &index_metadata, + index_process_sink, + ) + .await?; + } + }, } } diff --git a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs index dbab55610d..8da2505e75 100644 --- a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs +++ b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs @@ -16,15 +16,19 @@ use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; use crate::local_ai::stream_util::LocalAIStreamAdaptor; +use crate::stream_message::StreamMessage; +use flowy_storage_pub::storage::StorageService; +use futures_util::SinkExt; use serde_json::json; use std::path::Path; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use tracing::trace; pub struct AICloudServiceMiddleware { cloud_service: Arc, user_service: Arc, local_llm_controller: Arc, + storage_service: Weak, } impl AICloudServiceMiddleware { @@ -32,11 +36,13 @@ impl AICloudServiceMiddleware { user_service: Arc, cloud_service: Arc, local_llm_controller: Arc, + storage_service: Weak, ) -> Self { Self { user_service, cloud_service, local_llm_controller, + storage_service, } } @@ -50,10 +56,23 @@ impl AICloudServiceMiddleware { metadata_list: &[ChatMessageMetadata], index_process_sink: &mut (impl Sink + Unpin), ) -> Result<(), FlowyError> { - self - .local_llm_controller - .index_message_metadata(chat_id, metadata_list, index_process_sink) - .await?; + if metadata_list.is_empty() { + return Ok(()); + } + if self.is_local_ai_enabled() { + let _ = index_process_sink + .send(StreamMessage::IndexStart.to_string()) + .await; + self + .local_llm_controller + .index_message_metadata(chat_id, metadata_list, index_process_sink) + .await?; + let _ = index_process_sink + .send(StreamMessage::IndexEnd.to_string()) + .await; + } else if let Some(_storage_service) = self.storage_service.upgrade() { + // + } Ok(()) } @@ -110,17 +129,18 @@ impl ChatCloudService for AICloudServiceMiddleware { .await } - fn create_answer( + async fn create_answer( &self, workspace_id: &str, chat_id: &str, message: &str, question_id: i64, metadata: Option, - ) -> FutureResult { + ) -> Result { self .cloud_service .create_answer(workspace_id, chat_id, message, question_id, metadata) + .await } async fn stream_answer( @@ -184,16 +204,17 @@ impl ChatCloudService for AICloudServiceMiddleware { } } - fn get_chat_messages( + async fn get_chat_messages( &self, workspace_id: &str, chat_id: &str, offset: MessageCursor, limit: u64, - ) -> FutureResult { + ) -> Result { self .cloud_service .get_chat_messages(workspace_id, chat_id, offset, limit) + .await } async fn get_related_message( diff --git a/frontend/rust-lib/flowy-ai/src/stream_message.rs b/frontend/rust-lib/flowy-ai/src/stream_message.rs index d2b2b14100..c507262b85 100644 --- a/frontend/rust-lib/flowy-ai/src/stream_message.rs +++ b/frontend/rust-lib/flowy-ai/src/stream_message.rs @@ -1,22 +1,23 @@ use std::fmt::Display; pub enum StreamMessage { - MessageId { message_id: i64 }, + MessageId(i64), IndexStart, IndexEnd, - Text { text: String }, + Text(String), Done, StartIndexFile { file_name: String }, EndIndexFile { file_name: String }, IndexFileError { file_name: String }, } + impl Display for StreamMessage { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - StreamMessage::MessageId { message_id } => write!(f, "message_id:{}", message_id), + StreamMessage::MessageId(message_id) => write!(f, "message_id:{}", message_id), StreamMessage::IndexStart => write!(f, "index_start:"), StreamMessage::IndexEnd => write!(f, "index_end"), - StreamMessage::Text { text } => { + StreamMessage::Text(text) => { write!(f, "data:{}", text) }, StreamMessage::Done => write!(f, "done:"), diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs index 1cc7c5c490..c12cc0f181 100644 --- a/frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs +++ b/frontend/rust-lib/flowy-core/src/deps_resolve/chat_deps.rs @@ -3,6 +3,7 @@ use flowy_ai_pub::cloud::ChatCloudService; use flowy_error::FlowyError; use flowy_sqlite::kv::KVStorePreferences; use flowy_sqlite::DBConnection; +use flowy_storage_pub::storage::StorageService; use flowy_user::services::authenticate_user::AuthenticateUser; use std::path::PathBuf; use std::sync::{Arc, Weak}; @@ -14,12 +15,14 @@ impl ChatDepsResolver { authenticate_user: Weak, cloud_service: Arc, store_preferences: Arc, + storage_service: Weak, ) -> Arc { let user_service = ChatUserServiceImpl(authenticate_user); Arc::new(AIManager::new( cloud_service, user_service, store_preferences, + storage_service, )) } } diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index 26e17f2a5c..afb482d64a 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -629,24 +629,19 @@ impl ChatCloudService for ServerProvider { .await } - fn create_answer( + async fn create_answer( &self, workspace_id: &str, chat_id: &str, message: &str, question_id: i64, metadata: Option, - ) -> FutureResult { - let workspace_id = workspace_id.to_string(); - let chat_id = chat_id.to_string(); - let message = message.to_string(); + ) -> Result { let server = self.get_server(); - FutureResult::new(async move { - server? - .chat_service() - .create_answer(&workspace_id, &chat_id, &message, question_id, metadata) - .await - }) + server? + .chat_service() + .create_answer(workspace_id, chat_id, message, question_id, metadata) + .await } async fn stream_answer( @@ -664,22 +659,18 @@ impl ChatCloudService for ServerProvider { .await } - fn get_chat_messages( + async fn get_chat_messages( &self, workspace_id: &str, chat_id: &str, offset: MessageCursor, limit: u64, - ) -> FutureResult { - let workspace_id = workspace_id.to_string(); - let chat_id = chat_id.to_string(); - let server = self.get_server(); - FutureResult::new(async move { - server? - .chat_service() - .get_chat_messages(&workspace_id, &chat_id, offset, limit) - .await - }) + ) -> Result { + self + .get_server()? + .chat_service() + .get_chat_messages(workspace_id, chat_id, offset, limit) + .await } async fn get_related_message( diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index 2e72aa8469..761708bfe5 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -165,6 +165,7 @@ impl AppFlowyCore { Arc::downgrade(&authenticate_user), server_provider.clone(), store_preference.clone(), + Arc::downgrade(&storage_manager.storage_service), ); let database_manager = DatabaseDepsResolver::resolve( diff --git a/frontend/rust-lib/flowy-database2/Cargo.toml b/frontend/rust-lib/flowy-database2/Cargo.toml index cc32f944f5..f4acee0d4d 100644 --- a/frontend/rust-lib/flowy-database2/Cargo.toml +++ b/frontend/rust-lib/flowy-database2/Cargo.toml @@ -20,7 +20,7 @@ protobuf.workspace = true flowy-error = { path = "../flowy-error", features = [ "impl_from_dispatch_error", "impl_from_collab_database", -]} +] } lib-dispatch = { workspace = true } tokio = { workspace = true, features = ["sync"] } @@ -38,7 +38,7 @@ indexmap = { version = "2.1.0", features = ["serde"] } url = { version = "2" } fancy-regex = "0.11.0" futures.workspace = true -dashmap = "5" +dashmap.workspace = true anyhow.workspace = true async-stream = "0.3.4" rayon = "1.9.0" diff --git a/frontend/rust-lib/flowy-document/Cargo.toml b/frontend/rust-lib/flowy-document/Cargo.toml index e9bc84b8fe..f64c960b12 100644 --- a/frontend/rust-lib/flowy-document/Cargo.toml +++ b/frontend/rust-lib/flowy-document/Cargo.toml @@ -35,7 +35,7 @@ indexmap = { version = "2.1.0", features = ["serde"] } uuid.workspace = true futures.workspace = true tokio-stream = { workspace = true, features = ["sync"] } -dashmap = "5" +dashmap.workspace = true scraper = "0.18.0" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/frontend/rust-lib/flowy-document/src/event_handler.rs b/frontend/rust-lib/flowy-document/src/event_handler.rs index b99f51c8c5..66a98e3105 100644 --- a/frontend/rust-lib/flowy-document/src/event_handler.rs +++ b/frontend/rust-lib/flowy-document/src/event_handler.rs @@ -454,10 +454,17 @@ pub(crate) async fn upload_file_handler( } = params.try_into_inner()?; let manager = upgrade_document(manager)?; - let upload = manager - .upload_file(workspace_id, &document_id, &local_file_path) - .await?; + let (tx, rx) = tokio::sync::oneshot::channel(); + let cloned_local_file_path = local_file_path.clone(); + tokio::spawn(async move { + let result = manager + .upload_file(workspace_id, &document_id, &cloned_local_file_path) + .await; + let _ = tx.send(result); + Ok::<(), FlowyError>(()) + }); + let upload = rx.await??; data_result_ok(UploadedFilePB { url: upload.url, local_file_path, diff --git a/frontend/rust-lib/flowy-document/src/manager.rs b/frontend/rust-lib/flowy-document/src/manager.rs index 7718eead4e..5ea5aeb2de 100644 --- a/frontend/rust-lib/flowy-document/src/manager.rs +++ b/frontend/rust-lib/flowy-document/src/manager.rs @@ -358,8 +358,9 @@ impl DocumentManager { ) -> FlowyResult { let storage_service = self.storage_service_upgrade()?; let upload = storage_service - .create_upload(&workspace_id, document_id, local_file_path) - .await?; + .create_upload(&workspace_id, document_id, local_file_path, false) + .await? + .0; Ok(upload) } diff --git a/frontend/rust-lib/flowy-document/tests/document/util.rs b/frontend/rust-lib/flowy-document/tests/document/util.rs index ae2351ddd9..58663abd14 100644 --- a/frontend/rust-lib/flowy-document/tests/document/util.rs +++ b/frontend/rust-lib/flowy-document/tests/document/util.rs @@ -21,7 +21,7 @@ use flowy_document::manager::{DocumentManager, DocumentSnapshotService, Document use flowy_document_pub::cloud::*; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_storage_pub::chunked_byte::ChunkedBytes; -use flowy_storage_pub::storage::{CreatedUpload, StorageService}; +use flowy_storage_pub::storage::{CreatedUpload, FileProgressReceiver, StorageService}; use lib_infra::async_trait::async_trait; use lib_infra::box_any::BoxAny; use lib_infra::future::FutureResult; @@ -178,14 +178,6 @@ pub struct DocumentTestFileStorageService; #[async_trait] impl StorageService for DocumentTestFileStorageService { - fn upload_object( - &self, - _workspace_id: &str, - _local_file_path: &str, - ) -> FutureResult { - todo!() - } - fn delete_object(&self, _url: String, _local_file_path: String) -> FlowyResult<()> { todo!() } @@ -194,12 +186,13 @@ impl StorageService for DocumentTestFileStorageService { todo!() } - fn create_upload( + async fn create_upload( &self, _workspace_id: &str, _parent_dir: &str, _local_file_path: &str, - ) -> FutureResult { + _upload_immediately: bool, + ) -> Result<(CreatedUpload, Option), flowy_error::FlowyError> { todo!() } @@ -215,6 +208,10 @@ impl StorageService for DocumentTestFileStorageService { ) -> Result<(), FlowyError> { todo!() } + + async fn subscribe_file_progress(&self, _url: &str) -> Result { + todo!() + } } struct DefaultCollabStorageProvider(); diff --git a/frontend/rust-lib/flowy-notification/Cargo.toml b/frontend/rust-lib/flowy-notification/Cargo.toml index b459c9afbf..b7a96898ff 100644 --- a/frontend/rust-lib/flowy-notification/Cargo.toml +++ b/frontend/rust-lib/flowy-notification/Cargo.toml @@ -13,7 +13,7 @@ protobuf.workspace = true tracing.workspace = true bytes.workspace = true serde = { workspace = true, features = ["derive"] } -dashmap = "5.5" +dashmap.workspace = true tokio-util = "0.7" tokio = { workspace = true, features = ["time"] } diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs index 43fbc74cf0..3bdaf7e1a6 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs @@ -76,30 +76,25 @@ where Ok(message) } - fn create_answer( + async fn create_answer( &self, workspace_id: &str, chat_id: &str, message: &str, question_id: i64, metadata: Option, - ) -> FutureResult { - let workspace_id = workspace_id.to_string(); - let chat_id = chat_id.to_string(); + ) -> Result { let try_get_client = self.inner.try_get_client(); let params = CreateAnswerMessageParams { content: message.to_string(), metadata, question_message_id: question_id, }; - - FutureResult::new(async move { - let message = try_get_client? - .save_answer(&workspace_id, &chat_id, params) - .await - .map_err(FlowyError::from)?; - Ok(message) - }) + let message = try_get_client? + .save_answer(workspace_id, chat_id, params) + .await + .map_err(FlowyError::from)?; + Ok(message) } async fn stream_answer( @@ -131,25 +126,20 @@ where Ok(resp) } - fn get_chat_messages( + async fn get_chat_messages( &self, workspace_id: &str, chat_id: &str, offset: MessageCursor, limit: u64, - ) -> FutureResult { - let workspace_id = workspace_id.to_string(); - let chat_id = chat_id.to_string(); + ) -> Result { let try_get_client = self.inner.try_get_client(); + let resp = try_get_client? + .get_chat_messages(workspace_id, chat_id, offset, limit) + .await + .map_err(FlowyError::from)?; - FutureResult::new(async move { - let resp = try_get_client? - .get_chat_messages(&workspace_id, &chat_id, offset, limit) - .await - .map_err(FlowyError::from)?; - - Ok(resp) - }) + Ok(resp) } async fn get_related_message( diff --git a/frontend/rust-lib/flowy-server/src/default_impl.rs b/frontend/rust-lib/flowy-server/src/default_impl.rs index ff409faeae..be0ce1092e 100644 --- a/frontend/rust-lib/flowy-server/src/default_impl.rs +++ b/frontend/rust-lib/flowy-server/src/default_impl.rs @@ -34,17 +34,15 @@ impl ChatCloudService for DefaultChatCloudServiceImpl { Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) } - fn create_answer( + async fn create_answer( &self, _workspace_id: &str, _chat_id: &str, _message: &str, _question_id: i64, _metadata: Option, - ) -> FutureResult { - FutureResult::new(async move { - Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) - }) + ) -> Result { + Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) } async fn stream_answer( @@ -56,16 +54,14 @@ impl ChatCloudService for DefaultChatCloudServiceImpl { Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) } - fn get_chat_messages( + async fn get_chat_messages( &self, _workspace_id: &str, _chat_id: &str, _offset: MessageCursor, _limit: u64, - ) -> FutureResult { - FutureResult::new(async move { - Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) - }) + ) -> Result { + Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) } async fn get_related_message( diff --git a/frontend/rust-lib/flowy-storage-pub/Cargo.toml b/frontend/rust-lib/flowy-storage-pub/Cargo.toml index 3dee8f9f5e..ecab2212f8 100644 --- a/frontend/rust-lib/flowy-storage-pub/Cargo.toml +++ b/frontend/rust-lib/flowy-storage-pub/Cargo.toml @@ -17,3 +17,4 @@ mime_guess = "2.0.4" client-api-entity = { workspace = true } tokio = { workspace = true, features = ["sync", "io-util"] } anyhow = "1.0.86" +tracing.workspace = true diff --git a/frontend/rust-lib/flowy-storage-pub/src/storage.rs b/frontend/rust-lib/flowy-storage-pub/src/storage.rs index 8d40bef64c..30a9231dab 100644 --- a/frontend/rust-lib/flowy-storage-pub/src/storage.rs +++ b/frontend/rust-lib/flowy-storage-pub/src/storage.rs @@ -3,26 +3,23 @@ use async_trait::async_trait; pub use client_api_entity::{CompletedPartRequest, CreateUploadResponse, UploadPartResponse}; use flowy_error::{FlowyError, FlowyResult}; use lib_infra::box_any::BoxAny; -use lib_infra::future::FutureResult; +use std::ops::{Deref, DerefMut}; +use tokio::sync::mpsc; +use tracing::error; #[async_trait] pub trait StorageService: Send + Sync { - fn upload_object( - &self, - workspace_id: &str, - local_file_path: &str, - ) -> FutureResult; - fn delete_object(&self, url: String, local_file_path: String) -> FlowyResult<()>; fn download_object(&self, url: String, local_file_path: String) -> FlowyResult<()>; - fn create_upload( + async fn create_upload( &self, workspace_id: &str, parent_dir: &str, local_file_path: &str, - ) -> FutureResult; + upload_immediately: bool, + ) -> Result<(CreatedUpload, Option), FlowyError>; async fn start_upload(&self, chunks: &ChunkedBytes, record: &BoxAny) -> Result<(), FlowyError>; @@ -32,22 +29,70 @@ pub trait StorageService: Send + Sync { parent_dir: &str, file_id: &str, ) -> Result<(), FlowyError>; + + async fn subscribe_file_progress( + &self, + file_id: &str, + ) -> Result; +} + +pub struct FileProgressReceiver { + pub rx: mpsc::Receiver, + pub file_id: String, +} + +impl Deref for FileProgressReceiver { + type Target = mpsc::Receiver; + + fn deref(&self) -> &Self::Target { + &self.rx + } +} + +impl DerefMut for FileProgressReceiver { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.rx + } +} + +#[derive(Clone, Debug)] +pub enum FileUploadState { + NotStarted, + Uploading { progress: f64 }, + Finished { file_id: String }, +} + +#[derive(Debug)] +pub struct ProgressNotifier { + tx: mpsc::Sender, + pub current_value: Option, +} + +impl ProgressNotifier { + pub fn new() -> (Self, mpsc::Receiver) { + let (tx, rx) = mpsc::channel(5); + ( + ProgressNotifier { + tx, + current_value: None, + }, + rx, + ) + } + + pub async fn notify(&mut self, progress: FileUploadState) { + self.current_value = Some(progress.clone()); + // if self.tx.reserve().await.is_err() { + // return; + // } + + if let Err(err) = self.tx.send(progress).await { + error!("Failed to send progress notification: {:?}", err); + } + } } pub struct CreatedUpload { pub url: String, pub file_id: String, } - -#[derive(Debug, Clone)] -pub struct UploadResult { - pub file_id: String, - pub status: UploadStatus, -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum UploadStatus { - Finish, - Failed, - InProgress, -} diff --git a/frontend/rust-lib/flowy-storage/Cargo.toml b/frontend/rust-lib/flowy-storage/Cargo.toml index 8bbd24e4ff..d5a0ae0ff2 100644 --- a/frontend/rust-lib/flowy-storage/Cargo.toml +++ b/frontend/rust-lib/flowy-storage/Cargo.toml @@ -22,6 +22,7 @@ chrono = "0.4.33" flowy-notification = { workspace = true } flowy-derive.workspace = true protobuf = { workspace = true } +dashmap.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["full"] } diff --git a/frontend/rust-lib/flowy-storage/src/manager.rs b/frontend/rust-lib/flowy-storage/src/manager.rs index 0ae2751b5a..fb96e18529 100644 --- a/frontend/rust-lib/flowy-storage/src/manager.rs +++ b/frontend/rust-lib/flowy-storage/src/manager.rs @@ -7,16 +7,16 @@ use crate::sqlite_sql::{ }; use crate::uploader::{FileUploader, FileUploaderRunner, Signal, UploadTask, UploadTaskQueue}; use async_trait::async_trait; +use dashmap::DashMap; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_sqlite::DBConnection; use flowy_storage_pub::chunked_byte::{ChunkedBytes, MIN_CHUNK_SIZE}; use flowy_storage_pub::cloud::{ObjectIdentity, ObjectValue, StorageCloudService}; use flowy_storage_pub::storage::{ - CompletedPartRequest, CreatedUpload, StorageService, UploadPartResponse, UploadResult, - UploadStatus, + CompletedPartRequest, CreatedUpload, FileProgressReceiver, FileUploadState, ProgressNotifier, + StorageService, UploadPartResponse, }; use lib_infra::box_any::BoxAny; -use lib_infra::future::FutureResult; use lib_infra::util::timestamp; use std::io::ErrorKind; use std::path::{Path, PathBuf}; @@ -37,7 +37,7 @@ pub trait StorageUserService: Send + Sync + 'static { pub struct StorageManager { pub storage_service: Arc, uploader: Arc, - upload_status_notifier: tokio::sync::broadcast::Sender, + progress_notifiers: Arc>, } impl Drop for StorageManager { @@ -58,15 +58,15 @@ impl StorageManager { )); let temp_storage = Arc::new(FileTempStorage::new(temp_storage_path)); let (notifier, notifier_rx) = watch::channel(Signal::Proceed); - let (upload_status_notifier, _) = tokio::sync::broadcast::channel::(100); let task_queue = Arc::new(UploadTaskQueue::new(notifier)); + let progress_notifiers = Arc::new(DashMap::new()); let storage_service = Arc::new(StorageServiceImpl { cloud_service, user_service: user_service.clone(), temp_storage, task_queue: task_queue.clone(), - upload_status_notifier: upload_status_notifier.clone(), is_exceed_storage_limit: is_exceed_storage_limit.clone(), + progress_notifiers: progress_notifiers.clone(), }); let uploader = Arc::new(FileUploader::new( @@ -81,8 +81,8 @@ impl StorageManager { let weak_uploader = Arc::downgrade(&uploader); tokio::spawn(async move { - // Start uploading after 30 seconds - tokio::time::sleep(Duration::from_secs(30)).await; + // Start uploading after 20 seconds + tokio::time::sleep(Duration::from_secs(20)).await; if let Some(uploader) = weak_uploader.upgrade() { if let Err(err) = prepare_upload_task(uploader, user_service).await { error!("prepare upload task failed: {}", err); @@ -93,7 +93,7 @@ impl StorageManager { Self { storage_service, uploader, - upload_status_notifier, + progress_notifiers, } } @@ -115,8 +115,18 @@ impl StorageManager { self.uploader.enable_storage_write(); } - pub fn subscribe_upload_result(&self) -> tokio::sync::broadcast::Receiver { - self.upload_status_notifier.subscribe() + pub async fn subscribe_file_state( + &self, + file_id: &str, + ) -> Result { + self.storage_service.subscribe_file_progress(file_id).await + } + + pub async fn get_file_state(&self, file_id: &str) -> Option { + self + .progress_notifiers + .get(file_id) + .and_then(|notifier| notifier.value().current_value.clone()) } } @@ -147,37 +157,12 @@ pub struct StorageServiceImpl { user_service: Arc, temp_storage: Arc, task_queue: Arc, - upload_status_notifier: tokio::sync::broadcast::Sender, is_exceed_storage_limit: Arc, + progress_notifiers: Arc>, } #[async_trait] impl StorageService for StorageServiceImpl { - fn upload_object( - &self, - workspace_id: &str, - local_file_path: &str, - ) -> FutureResult { - let cloud_service = self.cloud_service.clone(); - let workspace_id = workspace_id.to_string(); - let local_file_path = local_file_path.to_string(); - FutureResult::new(async move { - let (object_identity, object_value) = - object_from_disk(&workspace_id, &local_file_path).await?; - let url = cloud_service.get_object_url(object_identity).await?; - match cloud_service.put_object(url.clone(), object_value).await { - Ok(_) => { - debug!("[File] success uploaded file to cloud: {}", url); - }, - Err(err) => { - error!("[File] upload file failed: {}", err); - return Err(err); - }, - } - Ok(url) - }) - } - fn delete_object(&self, url: String, local_file_path: String) -> FlowyResult<()> { let cloud_service = self.cloud_service.clone(); tokio::spawn(async move { @@ -227,93 +212,106 @@ impl StorageService for StorageServiceImpl { Ok(()) } - fn create_upload( + async fn create_upload( &self, workspace_id: &str, parent_dir: &str, file_path: &str, - ) -> FutureResult { + upload_immediately: bool, + ) -> Result<(CreatedUpload, Option), FlowyError> { if workspace_id.is_empty() { - return FutureResult::new(async { - Err(FlowyError::internal().with_context("workspace id is empty")) - }); + return Err(FlowyError::internal().with_context("workspace id is empty")); } if parent_dir.is_empty() { - return FutureResult::new(async { - Err(FlowyError::internal().with_context("parent dir is empty")) - }); + return Err(FlowyError::internal().with_context("parent dir is empty")); } if file_path.is_empty() { - return FutureResult::new(async { - Err(FlowyError::internal().with_context("local file path is empty")) - }); + return Err(FlowyError::internal().with_context("local file path is empty")); } let workspace_id = workspace_id.to_string(); let parent_dir = parent_dir.to_string(); let file_path = file_path.to_string(); - let temp_storage = self.temp_storage.clone(); - let task_queue = self.task_queue.clone(); - let user_service = self.user_service.clone(); - let cloud_service = self.cloud_service.clone(); - let is_exceed_storage_limit = self.is_exceed_storage_limit.clone(); - FutureResult::new(async move { - let is_exceed_limit = is_exceed_storage_limit.load(std::sync::atomic::Ordering::Relaxed); - if is_exceed_limit { - make_notification(StorageNotification::FileStorageLimitExceeded) - .payload(FlowyError::file_storage_limit()) - .send(); + let is_exceed_limit = self + .is_exceed_storage_limit + .load(std::sync::atomic::Ordering::Relaxed); + if is_exceed_limit { + make_notification(StorageNotification::FileStorageLimitExceeded) + .payload(FlowyError::file_storage_limit()) + .send(); - return Err(FlowyError::file_storage_limit()); - } + return Err(FlowyError::file_storage_limit()); + } - let local_file_path = temp_storage - .create_temp_file_from_existing(Path::new(&file_path)) - .await - .map_err(|err| { - error!("[File] create temp file failed: {}", err); - FlowyError::internal() - .with_context(format!("create temp file for upload file failed: {}", err)) - })?; + let local_file_path = self + .temp_storage + .create_temp_file_from_existing(Path::new(&file_path)) + .await + .map_err(|err| { + error!("[File] create temp file failed: {}", err); + FlowyError::internal() + .with_context(format!("create temp file for upload file failed: {}", err)) + })?; - // 1. create a file record and chunk the file - let (chunks, record) = - create_upload_record(workspace_id, parent_dir, local_file_path).await?; + // 1. create a file record and chunk the file + let (chunks, record) = create_upload_record(workspace_id, parent_dir, local_file_path).await?; - // 2. save the record to sqlite - let conn = user_service.sqlite_connection(user_service.user_id()?)?; - let url = cloud_service.get_object_url_v1( - &record.workspace_id, - &record.parent_dir, - &record.file_id, - )?; - let file_id = record.file_id.clone(); - match insert_upload_file(conn, &record) { - Ok(_) => { - // 3. generate url for given file - task_queue + // 2. save the record to sqlite + let conn = self + .user_service + .sqlite_connection(self.user_service.user_id()?)?; + let url = self.cloud_service.get_object_url_v1( + &record.workspace_id, + &record.parent_dir, + &record.file_id, + )?; + let file_id = record.file_id.clone(); + match insert_upload_file(conn, &record) { + Ok(_) => { + // 3. generate url for given file + if upload_immediately { + self + .task_queue + .queue_task(UploadTask::ImmediateTask { + chunks, + record, + retry_count: 3, + }) + .await; + } else { + self + .task_queue .queue_task(UploadTask::Task { chunks, record, retry_count: 0, }) .await; + } - Ok::<_, FlowyError>(CreatedUpload { url, file_id }) - }, - Err(err) => { - if matches!(err.code, ErrorCode::DuplicateSqliteRecord) { - info!("upload record already exists, skip creating new upload task"); - Ok::<_, FlowyError>(CreatedUpload { url, file_id }) - } else { - Err(err) - } - }, - } - }) + let (notifier, receiver) = ProgressNotifier::new(); + let receiver = FileProgressReceiver { + rx: receiver, + file_id: file_id.to_string(), + }; + self + .progress_notifiers + .insert(file_id.to_string(), notifier); + + Ok::<_, FlowyError>((CreatedUpload { url, file_id }, Some(receiver))) + }, + Err(err) => { + if matches!(err.code, ErrorCode::DuplicateSqliteRecord) { + info!("upload record already exists, skip creating new upload task"); + Ok::<_, FlowyError>((CreatedUpload { url, file_id }, None)) + } else { + Err(err) + } + }, + } } async fn start_upload(&self, chunks: &ChunkedBytes, record: &BoxAny) -> Result<(), FlowyError> { @@ -327,7 +325,7 @@ impl StorageService for StorageServiceImpl { &self.temp_storage, chunks, file_record, - self.upload_status_notifier.clone(), + self.progress_notifiers.clone(), ) .await { @@ -371,7 +369,7 @@ impl StorageService for StorageServiceImpl { &self.temp_storage, upload_file, parts, - self.upload_status_notifier.clone(), + self.progress_notifiers.clone(), ) .await?; } else { @@ -379,6 +377,22 @@ impl StorageService for StorageServiceImpl { } Ok(()) } + + async fn subscribe_file_progress( + &self, + file_id: &str, + ) -> Result { + trace!("[File]: subscribe file progress: {}", file_id); + let (notifier, receiver) = ProgressNotifier::new(); + let receiver = FileProgressReceiver { + rx: receiver, + file_id: file_id.to_string(), + }; + self + .progress_notifiers + .insert(file_id.to_string(), notifier); + Ok(receiver) + } } async fn create_upload_record( @@ -418,7 +432,7 @@ async fn start_upload( temp_storage: &Arc, chunked_bytes: &ChunkedBytes, upload_file: &UploadFileTable, - notifier: tokio::sync::broadcast::Sender, + progress_notifiers: Arc>, ) -> FlowyResult<()> { let mut upload_file = upload_file.clone(); if upload_file.upload_id.is_empty() { @@ -466,22 +480,32 @@ async fn start_upload( upload_file.upload_id = create_upload_resp.upload_id; } - let _ = notifier.send(UploadResult { - file_id: upload_file.file_id.clone(), - status: UploadStatus::InProgress, - }); - // 3. start uploading parts trace!( "[File] {} start uploading parts: {}", upload_file.file_id, chunked_bytes.iter().count() ); + let total_parts = chunked_bytes.iter().count(); let iter = chunked_bytes.iter().enumerate(); - let mut completed_parts = Vec::new(); + + let mut conn = user_service.sqlite_connection(user_service.user_id()?)?; + + // 4. gather existing completed parts + let mut completed_parts = select_upload_parts(&mut conn, &upload_file.upload_id) + .unwrap_or_default() + .into_iter() + .map(|part| CompletedPartRequest { + e_tag: part.e_tag, + part_number: part.part_num, + }) + .collect::>(); + + // when there are any existing parts, skip those parts by setting the current offset. + let offset = completed_parts.len(); for (index, chunk_bytes) in iter { - let part_number = index as i32 + 1; + let part_number = offset + index + 1; trace!( "[File] {} uploading part: {}, len:{}KB", upload_file.file_id, @@ -496,18 +520,24 @@ async fn start_upload( &upload_file.parent_dir, &upload_file.upload_id, &upload_file.file_id, - part_number, + part_number as i32, chunk_bytes.to_vec(), ) .await { Ok(resp) => { + let progress = (part_number as f64 / total_parts as f64).clamp(0.0, 1.0); trace!( - "[File] {} upload {} part success, total:{},", + "[File] {} upload progress: {}", upload_file.file_id, - part_number, - chunked_bytes.offsets.len() + progress ); + if let Some(mut notifier) = progress_notifiers.get_mut(&upload_file.file_id) { + notifier + .notify(FileUploadState::Uploading { progress }) + .await; + } + // gather completed part completed_parts.push(CompletedPartRequest { e_tag: resp.e_tag, @@ -534,7 +564,7 @@ async fn start_upload( temp_storage, &upload_file, completed_parts, - notifier, + &progress_notifiers, ) .await; if let Err(err) = complete_upload_result { @@ -556,7 +586,7 @@ async fn resume_upload( temp_storage: &Arc, upload_file: UploadFileTable, parts: Vec, - notifier: tokio::sync::broadcast::Sender, + progress_notifiers: Arc>, ) -> FlowyResult<()> { trace!( "[File] resume upload for workspace: {}, parent_dir: {}, file_id: {}, local_file_path:{}", @@ -576,7 +606,7 @@ async fn resume_upload( temp_storage, &chunked_bytes, &upload_file, - notifier, + progress_notifiers, ) .await?; }, @@ -643,8 +673,13 @@ async fn complete_upload( temp_storage: &Arc, upload_file: &UploadFileTable, parts: Vec, - notifier: tokio::sync::broadcast::Sender, + progress_notifiers: &Arc>, ) -> Result<(), FlowyError> { + trace!( + "[File]: completing file upload: {}, part: {}", + upload_file.file_id, + parts.len() + ); match cloud_service .complete_upload( &upload_file.workspace_id, @@ -656,13 +691,17 @@ async fn complete_upload( .await { Ok(_) => { - info!("[File] completed upload file: {}", upload_file.upload_id); - trace!("[File] delete upload record from sqlite"); - let _ = notifier.send(UploadResult { - file_id: upload_file.file_id.clone(), - status: UploadStatus::Finish, - }); + info!("[File] completed upload file: {}", upload_file.file_id); + if let Some(mut notifier) = progress_notifiers.get_mut(&upload_file.file_id) { + trace!("[File]: notify upload finished"); + notifier + .notify(FileUploadState::Finished { + file_id: upload_file.file_id.clone(), + }) + .await; + } + trace!("[File] delete upload record from sqlite"); let conn = user_service.sqlite_connection(user_service.user_id()?)?; delete_upload_file(conn, &upload_file.upload_id)?; if let Err(err) = temp_storage diff --git a/frontend/rust-lib/flowy-storage/src/uploader.rs b/frontend/rust-lib/flowy-storage/src/uploader.rs index f05878c7f8..7a92f24e03 100644 --- a/frontend/rust-lib/flowy-storage/src/uploader.rs +++ b/frontend/rust-lib/flowy-storage/src/uploader.rs @@ -153,7 +153,12 @@ impl FileUploader { .fetch_add(1, std::sync::atomic::Ordering::SeqCst); match task { - UploadTask::Task { + UploadTask::ImmediateTask { + chunks, + record, + mut retry_count, + } + | UploadTask::Task { chunks, record, mut retry_count, @@ -253,6 +258,11 @@ impl FileUploaderRunner { } pub enum UploadTask { + ImmediateTask { + chunks: ChunkedBytes, + record: UploadFileTable, + retry_count: u8, + }, Task { chunks: ChunkedBytes, record: UploadFileTable, @@ -270,8 +280,9 @@ pub enum UploadTask { impl UploadTask { pub fn retry_count(&self) -> u8 { match self { - Self::Task { retry_count, .. } => *retry_count, - Self::BackgroundTask { retry_count, .. } => *retry_count, + UploadTask::ImmediateTask { retry_count, .. } => *retry_count, + UploadTask::Task { retry_count, .. } => *retry_count, + UploadTask::BackgroundTask { retry_count, .. } => *retry_count, } } } @@ -279,8 +290,9 @@ impl UploadTask { impl Display for UploadTask { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Task { record, .. } => write!(f, "Task: {}", record.file_id), - Self::BackgroundTask { file_id, .. } => write!(f, "BackgroundTask: {}", file_id), + UploadTask::Task { record, .. } => write!(f, "Task: {}", record.file_id), + UploadTask::BackgroundTask { file_id, .. } => write!(f, "BackgroundTask: {}", file_id), + UploadTask::ImmediateTask { record, .. } => write!(f, "Immediate Task: {}", record.file_id), } } } @@ -290,6 +302,9 @@ impl Eq for UploadTask {} impl PartialEq for UploadTask { fn eq(&self, other: &Self) -> bool { match (self, other) { + (Self::ImmediateTask { record: lhs, .. }, Self::ImmediateTask { record: rhs, .. }) => { + lhs.local_file_path == rhs.local_file_path + }, (Self::Task { record: lhs, .. }, Self::Task { record: rhs, .. }) => { lhs.local_file_path == rhs.local_file_path }, @@ -319,6 +334,11 @@ impl PartialOrd for UploadTask { impl Ord for UploadTask { fn cmp(&self, other: &Self) -> Ordering { match (self, other) { + (Self::ImmediateTask { record: lhs, .. }, Self::ImmediateTask { record: rhs, .. }) => { + lhs.created_at.cmp(&rhs.created_at) + }, + (_, Self::ImmediateTask { .. }) => Ordering::Less, + (Self::ImmediateTask { .. }, _) => Ordering::Greater, (Self::Task { record: lhs, .. }, Self::Task { record: rhs, .. }) => { lhs.created_at.cmp(&rhs.created_at) }, From f7adcae8ffd45175017c1d90c1a0961df93d64b9 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:21:44 +0800 Subject: [PATCH 11/28] chore: show attachment on local ai (#5929) * chore: show attachment on local ai * chore: fix compile --- .../application/chat_ai_message_bloc.dart | 4 +-- .../ai_chat/application/chat_bloc.dart | 27 ++++++++++++----- .../ai_chat/application/chat_entity.dart | 10 +++++-- .../application/chat_message_service.dart | 29 ++++++++++++++----- .../application/chat_message_stream.dart | 2 +- .../chat_user_message_bubble_bloc.dart | 16 ++++++++-- .../lib/plugins/ai_chat/chat_page.dart | 7 ++--- .../presentation/message/ai_text_message.dart | 6 ++-- .../message/user_message_bubble.dart | 2 -- .../message/user_text_message.dart | 2 -- .../src/middleware/chat_service_mw.rs | 1 + 11 files changed, 74 insertions(+), 32 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart index 9083e40521..19fff60bbf 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart @@ -16,13 +16,13 @@ part 'chat_ai_message_bloc.freezed.dart'; class ChatAIMessageBloc extends Bloc { ChatAIMessageBloc({ dynamic message, - String? metadata, + String? refSourceJsonString, required this.chatId, required this.questionId, }) : super( ChatAIMessageState.initial( message, - messageRefSourceFromString(metadata), + messageReferenceSource(refSourceJsonString), ), ) { if (state.stream != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart index cd5579d7d4..09bf21acaa 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart @@ -374,7 +374,10 @@ class ChatBloc extends Bloc { metadata: await metadataPBFromMetadata(metadata), ); - final questionStreamMessage = _createQuestionStreamMessage(questionStream); + final questionStreamMessage = _createQuestionStreamMessage( + questionStream, + metadata, + ); add(ChatEvent.receveMessage(questionStreamMessage)); // Stream message to the server @@ -432,14 +435,24 @@ class ChatBloc extends Bloc { ); } - Message _createQuestionStreamMessage(QuestionStream stream) { + Message _createQuestionStreamMessage( + QuestionStream stream, + Map? sentMetadata, + ) { questionStreamMessageId = nanoid(); + final Map metadata = {}; + + // if (sentMetadata != null) { + // metadata[messageMetadataJsonStringKey] = sentMetadata; + // } + + metadata["$QuestionStream"] = stream; + metadata["chatId"] = chatId; + metadata[messageChatFileListKey] = + chatFilesFromMessageMetadata(sentMetadata); return TextMessage( author: User(id: state.userProfile.id.toString()), - metadata: { - "$QuestionStream": stream, - "chatId": chatId, - }, + metadata: metadata, id: questionStreamMessageId, createdAt: DateTime.now().millisecondsSinceEpoch, text: '', @@ -460,7 +473,7 @@ class ChatBloc extends Bloc { text: message.content, createdAt: message.createdAt.toInt() * 1000, metadata: { - messageMetadataKey: message.metadata, + messageRefSourceJsonStringKey: message.metadata, }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart index 99b6b66bb1..c0a2d5bf32 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart @@ -15,8 +15,14 @@ part 'chat_entity.freezed.dart'; const sendMessageErrorKey = "sendMessageError"; const systemUserId = "system"; const aiResponseUserId = "0"; -const messageMetadataKey = "metadata"; -const messageQuestionIdKey = "question"; + +/// `messageRefSourceJsonStringKey` is the key used for metadata that contains the reference source of a message. +/// Each message may include this information. +/// - When used in a sent message, it indicates that the message includes an attachment. +/// - When used in a received message, it indicates the AI reference sources used to answer a question. +const messageRefSourceJsonStringKey = "ref_source_json_string"; +const messageChatFileListKey = "chat_files"; +const messageQuestionIdKey = "question_id"; @JsonSerializable() class ChatMessageRefSource { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart index a2cfc9f986..c497cd6c23 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart @@ -32,7 +32,12 @@ List chatFilesFromMetadataString(String? s) { final metadataJson = jsonDecode(s); if (metadataJson is Map) { - return _parseChatFile(metadataJson); + final file = chatFileFromMap(metadataJson); + if (file != null) { + return [file]; + } else { + return []; + } } else if (metadataJson is List) { return metadataJson .map((e) => e as Map) @@ -46,11 +51,6 @@ List chatFilesFromMetadataString(String? s) { } } -List _parseChatFile(Map map) { - final file = chatFileFromMap(map); - return file != null ? [file] : []; -} - ChatFile? chatFileFromMap(Map? map) { if (map == null) return null; @@ -63,7 +63,7 @@ ChatFile? chatFileFromMap(Map? map) { return ChatFile.fromFilePath(filePath); } -List messageRefSourceFromString(String? s) { +List messageReferenceSource(String? s) { if (s == null || s.isEmpty || s == "null") { return []; } @@ -139,3 +139,18 @@ Future> metadataPBFromMetadata( return metadata; } + +List chatFilesFromMessageMetadata( + Map? map, +) { + final List metadata = []; + if (map != null) { + for (final entry in map.entries) { + if (entry.value is ChatFile) { + metadata.add(entry.value); + } + } + } + + return metadata; +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart index a82a84f34a..438f3874e2 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart @@ -25,7 +25,7 @@ class AnswerStream { } else if (event.startsWith("metadata:")) { if (_onMetadata != null) { final s = event.substring(9); - _onMetadata!(messageRefSourceFromString(s)); + _onMetadata!(messageReferenceSource(s)); } } else if (event == "AI_RESPONSE_LIMIT") { if (_onAIResponseLimit != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart index 542280ca99..c4571915df 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bubble_bloc.dart @@ -11,11 +11,10 @@ class ChatUserMessageBubbleBloc extends Bloc { ChatUserMessageBubbleBloc({ required Message message, - required String? metadata, }) : super( ChatUserMessageBubbleState.initial( message, - chatFilesFromMetadataString(metadata), + _getFiles(message.metadata), ), ) { on( @@ -28,6 +27,19 @@ class ChatUserMessageBubbleBloc } } +List _getFiles(Map? metadata) { + if (metadata == null) { + return []; + } + final refSourceMetadata = metadata[messageRefSourceJsonStringKey] as String?; + final files = metadata[messageChatFileListKey] as List?; + + if (refSourceMetadata != null) { + return chatFilesFromMetadataString(refSourceMetadata); + } + return files ?? []; +} + @freezed class ChatUserMessageBubbleEvent with _$ChatUserMessageBubbleEvent { const factory ChatUserMessageBubbleEvent.initial() = Initial; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart index 6ad35d08db..fced505f20 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -320,17 +320,16 @@ class _ChatContentPageState extends State<_ChatContentPage> { Widget _buildTextMessage(BuildContext context, TextMessage message) { if (message.author.id == _user.id) { final stream = message.metadata?["$QuestionStream"]; - final metadata = message.metadata?[messageMetadataKey] as String?; return ChatUserMessageWidget( key: ValueKey(message.id), user: message.author, message: stream is QuestionStream ? stream : message.text, - metadata: metadata, ); } else { final stream = message.metadata?["$AnswerStream"]; final questionId = message.metadata?[messageQuestionIdKey]; - final metadata = message.metadata?[messageMetadataKey] as String?; + final refSourceJsonString = + message.metadata?[messageRefSourceJsonStringKey] as String?; return ChatAIMessageWidget( user: message.author, messageUserId: message.id, @@ -338,7 +337,7 @@ class _ChatContentPageState extends State<_ChatContentPage> { key: ValueKey(message.id), questionId: questionId, chatId: widget.view.id, - metadata: metadata, + refSourceJsonString: refSourceJsonString, onSelectedMetadata: (ChatMessageRefSource metadata) { context.read().add( ChatSidePannelEvent.selectedMetadata(metadata), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart index ed928eba14..7aa48b094f 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart @@ -22,7 +22,7 @@ class ChatAIMessageWidget extends StatelessWidget { required this.message, required this.questionId, required this.chatId, - required this.metadata, + required this.refSourceJsonString, required this.onSelectedMetadata, }); @@ -33,7 +33,7 @@ class ChatAIMessageWidget extends StatelessWidget { final dynamic message; final Int64? questionId; final String chatId; - final String? metadata; + final String? refSourceJsonString; final void Function(ChatMessageRefSource metadata) onSelectedMetadata; @override @@ -41,7 +41,7 @@ class ChatAIMessageWidget extends StatelessWidget { return BlocProvider( create: (context) => ChatAIMessageBloc( message: message, - metadata: metadata, + refSourceJsonString: refSourceJsonString, chatId: chatId, questionId: questionId, )..add(const ChatAIMessageEvent.initial()), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart index 06401b76a7..08c627b0b7 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart @@ -29,12 +29,10 @@ class ChatUserMessageBubble extends StatelessWidget { .read() .add(ChatMemberEvent.getMemberInfo(message.author.id)); } - final metadata = message.metadata?[messageMetadataKey] as String?; return BlocProvider( create: (context) => ChatUserMessageBubbleBloc( message: message, - metadata: metadata, ), child: BlocBuilder( builder: (context, state) { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart index 0b2a2efa7d..c804441188 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart @@ -10,12 +10,10 @@ class ChatUserMessageWidget extends StatelessWidget { super.key, required this.user, required this.message, - required this.metadata, }); final User user; final dynamic message; - final String? metadata; @override Widget build(BuildContext context) { diff --git a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs index 8da2505e75..2e7f4e54a2 100644 --- a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs +++ b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs @@ -63,6 +63,7 @@ impl AICloudServiceMiddleware { let _ = index_process_sink .send(StreamMessage::IndexStart.to_string()) .await; + self .local_llm_controller .index_message_metadata(chat_id, metadata_list, index_process_sink) From 2bc7875bdd8eb99415fa79a11a6f58fe674f5220 Mon Sep 17 00:00:00 2001 From: Annie Date: Mon, 12 Aug 2024 13:52:53 +0800 Subject: [PATCH 12/28] chore: polish upload image menu (#5928) * chore: polish upload image menu * fix: text align issue --------- Co-authored-by: Lucas.Xu --- .../upload_image_menu/upload_image_menu.dart | 38 +++++++++++-------- .../widgets/embed_image_url_widget.dart | 18 ++++++--- .../slash_menu/slash_menu_items.dart | 2 +- .../flowy_infra_ui/lib/style_widget/text.dart | 9 +++-- 4 files changed, 41 insertions(+), 26 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart index 6e668a165b..cdda330115 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart @@ -150,25 +150,33 @@ class _UploadImageMenuState extends State { final type = values[currentTabIndex]; switch (type) { case UploadImageType.local: - return Container( + return Padding( padding: const EdgeInsets.all(8.0), - alignment: Alignment.center, - constraints: constraints, - child: Column( - children: [ - UploadImageFileWidget( - allowMultipleImages: widget.allowMultipleImages, - onPickFiles: widget.onSelectedLocalImages, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).colorScheme.outline, ), - if (widget.limitMaximumImageSize) ...[ - const VSpace(6.0), - FlowyText( - LocaleKeys.document_imageBlock_maximumImageSize.tr(), - fontSize: 12.0, - color: Theme.of(context).hintColor, + ), + constraints: constraints, + child: Column( + children: [ + UploadImageFileWidget( + allowMultipleImages: widget.allowMultipleImages, + onPickFiles: widget.onSelectedLocalImages, ), + if (widget.limitMaximumImageSize) ...[ + const VSpace(6.0), + FlowyText( + LocaleKeys.document_imageBlock_maximumImageSize.tr(), + fontSize: 12.0, + color: Theme.of(context).hintColor, + ), + ], ], - ], + ), ), ); case UploadImageType.url: diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart index d34a4fc3a8..d84dcab318 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class EmbedImageUrlWidget extends StatefulWidget { const EmbedImageUrlWidget({ @@ -25,31 +24,38 @@ class _EmbedImageUrlWidgetState extends State { Widget build(BuildContext context) { return Column( children: [ + const VSpace(12), FlowyTextField( hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(), onChanged: (value) => inputText = value, onEditingComplete: submit, ), if (!isUrlValid) ...[ - const VSpace(8), + const VSpace(12), FlowyText( LocaleKeys.document_plugins_cover_invalidImageUrl.tr(), color: Theme.of(context).colorScheme.error, ), ], - const VSpace(8), + const VSpace(20), SizedBox( - width: 160, + height: 32, + width: 300, child: FlowyButton( + backgroundColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), showDefaultBoxDecorationOnMobile: true, - margin: const EdgeInsets.all(8.0), + margin: const EdgeInsets.all(5), text: FlowyText( LocaleKeys.document_imageBlock_embedLink_label.tr(), + lineHeight: 1, textAlign: TextAlign.center, + color: Theme.of(context).colorScheme.onPrimary, ), onTap: submit, ), ), + const VSpace(8), ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart index 6b1163c79d..8b92d51ab2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items.dart @@ -448,7 +448,7 @@ Widget _slashMenuItemNameBuilder( SelectionMenuStyle style, bool isSelected, ) { - return FlowyText( + return FlowyText.regular( name, fontSize: 12.0, figmaLineHeight: 15.0, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 036de080b3..5da368870f 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -167,10 +167,11 @@ class FlowyText extends StatelessWidget { } double? lineHeight; - if (this.lineHeight != null) { - lineHeight = this.lineHeight!; - } else if (figmaLineHeight != null) { + // use figma line height as first priority + if (figmaLineHeight != null) { lineHeight = figmaLineHeight! / fontSize; + } else if (this.lineHeight != null) { + lineHeight = this.lineHeight!; } if (isEmoji && (_useNotoColorEmoji || Platform.isWindows)) { @@ -210,7 +211,7 @@ class FlowyText extends StatelessWidget { textAlign: textAlign, overflow: overflow ?? TextOverflow.clip, style: textStyle, - strutStyle: (isEmoji && optimizeEmojiAlign) + strutStyle: !isEmoji || (isEmoji && optimizeEmojiAlign) ? StrutStyle.fromTextStyle( textStyle, forceStrutHeight: true, From 55a4810d6006cbbc54504a7cfcfbbeb873c5f6af Mon Sep 17 00:00:00 2001 From: Sota Date: Mon, 12 Aug 2024 00:12:52 -0700 Subject: [PATCH 13/28] =?UTF-8?q?chore:=20update=20translations=20with=20F?= =?UTF-8?q?ink=20=F0=9F=90=A6=20(#5932)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/resources/translations/ar-SA.json | 2 +- frontend/resources/translations/ca-ES.json | 2 +- frontend/resources/translations/ckb-KU.json | 2 +- frontend/resources/translations/cs-CZ.json | 2 +- frontend/resources/translations/de-DE.json | 2 +- frontend/resources/translations/es-VE.json | 2 +- frontend/resources/translations/eu-ES.json | 2 +- frontend/resources/translations/fa.json | 2 +- frontend/resources/translations/fr-CA.json | 2 +- frontend/resources/translations/fr-FR.json | 2 +- frontend/resources/translations/he.json | 2 +- frontend/resources/translations/hu-HU.json | 2 +- frontend/resources/translations/id-ID.json | 2 +- frontend/resources/translations/it-IT.json | 2 +- frontend/resources/translations/ja-JP.json | 6 +++++- frontend/resources/translations/ko-KR.json | 2 +- frontend/resources/translations/pl-PL.json | 2 +- frontend/resources/translations/pt-BR.json | 2 +- frontend/resources/translations/pt-PT.json | 2 +- frontend/resources/translations/ru-RU.json | 2 +- frontend/resources/translations/sv-SE.json | 2 +- frontend/resources/translations/tr-TR.json | 2 +- frontend/resources/translations/uk-UA.json | 2 +- frontend/resources/translations/vi-VN.json | 2 +- frontend/resources/translations/vi.json | 2 +- frontend/resources/translations/zh-CN.json | 2 +- frontend/resources/translations/zh-TW.json | 2 +- 27 files changed, 31 insertions(+), 27 deletions(-) diff --git a/frontend/resources/translations/ar-SA.json b/frontend/resources/translations/ar-SA.json index ae6907a90b..5ab1c7107c 100644 --- a/frontend/resources/translations/ar-SA.json +++ b/frontend/resources/translations/ar-SA.json @@ -1169,4 +1169,4 @@ "addField": "إضافة حقل", "userIcon": "رمز المستخدم" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ca-ES.json b/frontend/resources/translations/ca-ES.json index a7a79854f7..3be6967df0 100644 --- a/frontend/resources/translations/ca-ES.json +++ b/frontend/resources/translations/ca-ES.json @@ -812,4 +812,4 @@ "deleteContentTitle": "Esteu segur que voleu suprimir {pageType}?", "deleteContentCaption": "si suprimiu aquest {pageType}, podeu restaurar-lo des de la paperera." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ckb-KU.json b/frontend/resources/translations/ckb-KU.json index 6971f83de8..264bf417c2 100644 --- a/frontend/resources/translations/ckb-KU.json +++ b/frontend/resources/translations/ckb-KU.json @@ -946,4 +946,4 @@ "frequentlyUsed": "زۆرجار بەکارت هێناوە" } } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/cs-CZ.json b/frontend/resources/translations/cs-CZ.json index 4c87b37498..02f2a59d44 100644 --- a/frontend/resources/translations/cs-CZ.json +++ b/frontend/resources/translations/cs-CZ.json @@ -1094,4 +1094,4 @@ "font": "Písmo", "actions": "Příkazy" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/de-DE.json b/frontend/resources/translations/de-DE.json index 9d3c47c873..caedb89e14 100644 --- a/frontend/resources/translations/de-DE.json +++ b/frontend/resources/translations/de-DE.json @@ -2441,4 +2441,4 @@ "commentAddedSuccessfully": "Kommentar erfolgreich hinzugefügt.", "commentAddedSuccessTip": "Du hast gerade einen Kommentar hinzugefügt oder darauf geantwortet. Möchtest du nach oben springen, um die neuesten Kommentare zu sehen?" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/es-VE.json b/frontend/resources/translations/es-VE.json index c5facc0f44..45b06906e7 100644 --- a/frontend/resources/translations/es-VE.json +++ b/frontend/resources/translations/es-VE.json @@ -1553,4 +1553,4 @@ "betaTooltip": "Actualmente solo admitimos la búsqueda de páginas.", "fromTrashHint": "De la papelera" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/eu-ES.json b/frontend/resources/translations/eu-ES.json index f5d7340102..031b9d9391 100644 --- a/frontend/resources/translations/eu-ES.json +++ b/frontend/resources/translations/eu-ES.json @@ -601,4 +601,4 @@ "deleteContentTitle": "Ziur {pageType} ezabatu nahi duzula?", "deleteContentCaption": "{pageType} hau ezabatzen baduzu, zaborrontzitik leheneratu dezakezu." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/fa.json b/frontend/resources/translations/fa.json index 21c8505c88..5baa50e8dd 100644 --- a/frontend/resources/translations/fa.json +++ b/frontend/resources/translations/fa.json @@ -674,4 +674,4 @@ "frequentlyUsed": "استفاده‌شده" } } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/fr-CA.json b/frontend/resources/translations/fr-CA.json index ce2aca57bd..9a3f512de6 100644 --- a/frontend/resources/translations/fr-CA.json +++ b/frontend/resources/translations/fr-CA.json @@ -1262,4 +1262,4 @@ "userIcon": "Icône utilisateur" }, "noLogFiles": "Il n'y a pas de log" -} +} \ No newline at end of file diff --git a/frontend/resources/translations/fr-FR.json b/frontend/resources/translations/fr-FR.json index 434c081c90..0283ca9a32 100644 --- a/frontend/resources/translations/fr-FR.json +++ b/frontend/resources/translations/fr-FR.json @@ -1573,4 +1573,4 @@ "loadingTooltip": "Nous recherchons des résultats...", "betaTooltip": "Nous ne prenons actuellement en charge que la recherche de pages" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/he.json b/frontend/resources/translations/he.json index 572e54a5fa..b3819e4182 100644 --- a/frontend/resources/translations/he.json +++ b/frontend/resources/translations/he.json @@ -2086,4 +2086,4 @@ "signInError": "שגיאת כניסה", "login": "הרשמה או כניסה" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/hu-HU.json b/frontend/resources/translations/hu-HU.json index 3fcae71dfc..78134fd8ab 100644 --- a/frontend/resources/translations/hu-HU.json +++ b/frontend/resources/translations/hu-HU.json @@ -599,4 +599,4 @@ "deleteContentTitle": "Biztosan törli a következőt: {pageType}?", "deleteContentCaption": "ha törli ezt a {pageType} oldalt, visszaállíthatja a kukából." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/id-ID.json b/frontend/resources/translations/id-ID.json index afeeb6ff50..011da14814 100644 --- a/frontend/resources/translations/id-ID.json +++ b/frontend/resources/translations/id-ID.json @@ -1022,4 +1022,4 @@ "noFavorite": "Tidak ada halaman favorit", "noFavoriteHintText": "Geser halaman ke kiri untuk menambahkannya ke favorit Anda" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/it-IT.json b/frontend/resources/translations/it-IT.json index dc2cfce89b..3d56f7196a 100644 --- a/frontend/resources/translations/it-IT.json +++ b/frontend/resources/translations/it-IT.json @@ -1262,4 +1262,4 @@ "userIcon": "Icona utente" }, "noLogFiles": "Non ci sono file di log" -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ja-JP.json b/frontend/resources/translations/ja-JP.json index 9646776f93..3de71dd7d7 100644 --- a/frontend/resources/translations/ja-JP.json +++ b/frontend/resources/translations/ja-JP.json @@ -2,6 +2,7 @@ "appName": "AppFlowy", "defaultUsername": "ユーザー", "welcomeText": "Welcome to @:appName", + "welcomeTo": "ようこそ", "githubStarText": "Star on GitHub", "subscribeNewsletterText": "新着情報を受け取る", "letsGoButtonText": "Let's Go", @@ -39,6 +40,8 @@ "dontHaveAnAccount": "まだアカウントをお持ちではないですか?", "repeatPasswordEmptyError": "パスワード(確認用)を空にはできません", "unmatchedPasswordError": "パスワード(確認用)が一致しません", + "syncPromptMessage": "データの同期に時間がかかる場合があります。ページを閉じないでください。", + "magicLinkSent": "マジックリンクが送信されました!", "LogInWithGoogle": "Googleでログイン", "LogInWithGithub": "GitHubでログイン", "LogInWithDiscord": "Discordでログイン", @@ -47,6 +50,7 @@ "workspace": { "chooseWorkspace": "ワークスベースを選択", "create": "ワークスペースを作成する", + "reset": "ワークスペースをリセットする", "hint": "ワークスペース", "notFoundError": "ワークスペースがみつかりません", "errorActions": { @@ -686,4 +690,4 @@ "deleteContentTitle": "{pageType} を削除してもよろしいですか?", "deleteContentCaption": "この {pageType} を削除しても、ゴミ箱から復元できます。" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ko-KR.json b/frontend/resources/translations/ko-KR.json index 823792b2a4..a9d97d944d 100644 --- a/frontend/resources/translations/ko-KR.json +++ b/frontend/resources/translations/ko-KR.json @@ -598,4 +598,4 @@ "deleteContentTitle": "{pageType}을(를) 삭제하시겠습니까?", "deleteContentCaption": "이 {pageType}을(를) 삭제하면 휴지통에서 복원할 수 있습니다." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/pl-PL.json b/frontend/resources/translations/pl-PL.json index fde0bb2f5c..1347de88e3 100644 --- a/frontend/resources/translations/pl-PL.json +++ b/frontend/resources/translations/pl-PL.json @@ -1146,4 +1146,4 @@ "language": "Język", "font": "Czcionka" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/pt-BR.json b/frontend/resources/translations/pt-BR.json index c0a9edbf88..fc7caabfc0 100644 --- a/frontend/resources/translations/pt-BR.json +++ b/frontend/resources/translations/pt-BR.json @@ -1219,4 +1219,4 @@ "addField": "Adicionar campo", "userIcon": "Ícone do usuário" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/pt-PT.json b/frontend/resources/translations/pt-PT.json index 4512a5f89b..62f60ef702 100644 --- a/frontend/resources/translations/pt-PT.json +++ b/frontend/resources/translations/pt-PT.json @@ -857,4 +857,4 @@ "noResult": "Nenhum resultado", "caseSensitive": "Maiúsculas e minúsculas" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/ru-RU.json b/frontend/resources/translations/ru-RU.json index 94dd06785d..45861fcd69 100644 --- a/frontend/resources/translations/ru-RU.json +++ b/frontend/resources/translations/ru-RU.json @@ -2103,4 +2103,4 @@ "signInError": "Ошибка входа", "login": "Зарегистрироваться или войти" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/sv-SE.json b/frontend/resources/translations/sv-SE.json index dc48bf92c8..6284a61d9a 100644 --- a/frontend/resources/translations/sv-SE.json +++ b/frontend/resources/translations/sv-SE.json @@ -668,4 +668,4 @@ "deleteContentTitle": "Är du säker på att du vill ta bort {pageType}?", "deleteContentCaption": "om du tar bort denna {pageType} kan du återställa den från papperskorgen." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/tr-TR.json b/frontend/resources/translations/tr-TR.json index d2039fc8f6..20975c33e8 100644 --- a/frontend/resources/translations/tr-TR.json +++ b/frontend/resources/translations/tr-TR.json @@ -2226,4 +2226,4 @@ "signInError": "Oturum açma hatası", "login": "Kaydolun veya giriş yapın" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/uk-UA.json b/frontend/resources/translations/uk-UA.json index 60b929e864..7aed531f3b 100644 --- a/frontend/resources/translations/uk-UA.json +++ b/frontend/resources/translations/uk-UA.json @@ -813,4 +813,4 @@ "noResult": "Немає результатів", "caseSensitive": "З урахуванням регістру" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/vi-VN.json b/frontend/resources/translations/vi-VN.json index ffc930f1a1..0f390ab2ff 100644 --- a/frontend/resources/translations/vi-VN.json +++ b/frontend/resources/translations/vi-VN.json @@ -868,4 +868,4 @@ "font": "Phông chữ", "date": "Ngày" } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/vi.json b/frontend/resources/translations/vi.json index b921c1844e..4d1716447a 100644 --- a/frontend/resources/translations/vi.json +++ b/frontend/resources/translations/vi.json @@ -6,4 +6,4 @@ "failedToLoad": "Không tải được chế độ xem bảng" } } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/zh-CN.json b/frontend/resources/translations/zh-CN.json index df2311106b..6ce16583aa 100644 --- a/frontend/resources/translations/zh-CN.json +++ b/frontend/resources/translations/zh-CN.json @@ -1621,4 +1621,4 @@ "commandPalette": { "placeholder": "输入你要搜索的内容..." } -} +} \ No newline at end of file diff --git a/frontend/resources/translations/zh-TW.json b/frontend/resources/translations/zh-TW.json index 452c498865..6db8fc7557 100644 --- a/frontend/resources/translations/zh-TW.json +++ b/frontend/resources/translations/zh-TW.json @@ -1470,4 +1470,4 @@ "betaLabel": "BETA", "betaTooltip": "目前我們只支援搜尋頁面" } -} +} \ No newline at end of file From 1c638dd9309b9be49f71ad04c3b313c13ab67f7f Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:43:17 +0800 Subject: [PATCH 14/28] chore: show reference sources on local ai response (#5933) * chore: show reference sources on local ai response * chore: clippy * chore: clippy * chore: update i18n * chore: update client api --- .../application/chat_message_service.dart | 6 +- .../application/chat_user_message_bloc.dart | 4 +- .../presentation/message/ai_metadata.dart | 9 ++- .../settings/ai/download_model_bloc.dart | 17 +++++- .../setting_ai_view/downloading_model.dart | 21 +++++-- .../settings/pages/settings_plan_view.dart | 14 ++--- frontend/appflowy_tauri/src-tauri/Cargo.lock | 59 ++++++++++++------- frontend/appflowy_tauri/src-tauri/Cargo.toml | 6 +- .../appflowy_web_app/src-tauri/Cargo.lock | 59 ++++++++++++------- .../appflowy_web_app/src-tauri/Cargo.toml | 6 +- frontend/resources/translations/en.json | 9 +-- frontend/rust-lib/Cargo.lock | 28 ++++----- frontend/rust-lib/Cargo.toml | 8 +-- frontend/rust-lib/flowy-ai-pub/src/cloud.rs | 3 + frontend/rust-lib/flowy-ai/src/chat.rs | 1 + .../flowy-ai/src/local_ai/local_llm_chat.rs | 1 + .../src/local_ai/local_llm_resource.rs | 4 ++ .../flowy-ai/src/local_ai/stream_util.rs | 43 ++++++++++---- .../src/middleware/chat_service_mw.rs | 12 ++-- .../flowy-core/src/integrate/trait_impls.rs | 5 +- .../flowy-server/src/af_cloud/impls/chat.rs | 4 +- .../rust-lib/flowy-server/src/default_impl.rs | 3 + 22 files changed, 220 insertions(+), 102 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart index c497cd6c23..1985e41242 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_service.dart @@ -10,6 +10,9 @@ import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:nanoid/nanoid.dart'; +/// Indicate file source from appflowy document +const appflowySoruce = "appflowy"; + List fileListFromMessageMetadata( Map? map, ) { @@ -75,6 +78,7 @@ List messageReferenceSource(String? s) { Log.warn("metadata is null"); return []; } + // [{"id":null,"name":"The Five Dysfunctions of a Team.pdf","source":"/Users/weidongfu/Desktop/The Five Dysfunctions of a Team.pdf"}] if (metadataJson is Map) { if (metadataJson.isNotEmpty) { @@ -115,7 +119,7 @@ Future> metadataPBFromMetadata( name: view.name, data: pb.text, dataType: ChatMessageMetaTypePB.Txt, - source: "appflowy", + source: appflowySoruce, ), ); }, (err) { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart index 207d811b8b..c0a29c2453 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart @@ -19,7 +19,9 @@ class ChatUserMessageBloc event.when( initial: () { if (state.stream != null) { - add(ChatUserMessageEvent.updateText(state.stream!.text)); + if (!isClosed) { + add(ChatUserMessageEvent.updateText(state.stream!.text)); + } } state.stream?.listen( diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart index 9796a28a34..6d4f85d616 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_message_service.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -53,7 +54,13 @@ class AIMessageMetadata extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - onTap: () => onSelectedMetadata(m), + disable: m.source != appflowySoruce, + onTap: () { + if (m.source != appflowySoruce) { + return; + } + onSelectedMetadata(m); + }, ), ), ) diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart index a14206e38e..0a1fce251b 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart @@ -2,18 +2,20 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; import 'package:bloc/bloc.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:fixnum/fixnum.dart'; part 'download_model_bloc.freezed.dart'; class DownloadModelBloc extends Bloc { DownloadModelBloc(LLMModelPB model) - : super(DownloadModelState(model: model)) { + : super(DownloadModelState.initial(model)) { on(_handleEvent); } @@ -99,8 +101,21 @@ class DownloadModelState with _$DownloadModelState { @Default("") String object, @Default(0) double percent, @Default(false) bool isFinish, + String? bigFileDownloadPrompt, @Default(ChatLoadingState.loading()) ChatLoadingState loadingState, }) = _DownloadModelState; + + factory DownloadModelState.initial(LLMModelPB model) { + // bigger than 1 GB then show download big file prompt + String? bigFileDownloadPrompt; + if (model.fileSize > 1 * 1024 * 1024 * 1024) { + bigFileDownloadPrompt = LocaleKeys.settings_aiPage_keys_downloadBigFilePrompt.tr(); + } + return DownloadModelState( + model: model, + bigFileDownloadPrompt: bigFileDownloadPrompt, + ); + } } class DownloadingStream { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart index dccc01dcc8..a7ed782aea 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart @@ -37,10 +37,23 @@ class DownloadingIndicator extends StatelessWidget { color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(8), ), - child: Column( - children: [ - DownloadingProgressBar(onCancel: onCancel), - ], + child: BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DownloadingProgressBar(onCancel: onCancel), + if (state.bigFileDownloadPrompt != null) ...[ + const VSpace(2), + Opacity( + opacity: 0.6, + child: + FlowyText(state.bigFileDownloadPrompt!, fontSize: 11), + ), + ], + ], + ); + }, ), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart index ac1fd2ab09..135259a04e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart @@ -121,8 +121,8 @@ class _SettingsPlanViewState extends State { priceInfo: LocaleKeys .settings_planPage_planUsage_addons_aiMax_priceInfo .tr(), - billingInfo: LocaleKeys - .settings_planPage_planUsage_addons_aiMax_billingInfo + recommend: LocaleKeys + .settings_planPage_planUsage_addons_aiMax_recommend .tr( args: [SubscriptionPlanPB.AiMax.priceMonthBilling], ), @@ -160,8 +160,8 @@ class _SettingsPlanViewState extends State { priceInfo: LocaleKeys .settings_planPage_planUsage_addons_aiOnDevice_priceInfo .tr(), - billingInfo: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_billingInfo + recommend: LocaleKeys + .settings_planPage_planUsage_addons_aiOnDevice_recommend .tr( args: [ SubscriptionPlanPB.AiLocal.priceMonthBilling, @@ -654,7 +654,7 @@ class _AddOnBox extends StatelessWidget { required this.description, required this.price, required this.priceInfo, - required this.billingInfo, + required this.recommend, required this.buttonText, required this.isActive, required this.plan, @@ -664,7 +664,7 @@ class _AddOnBox extends StatelessWidget { final String description; final String price; final String priceInfo; - final String billingInfo; + final String recommend; final String buttonText; final bool isActive; final SubscriptionPlanPB plan; @@ -717,7 +717,7 @@ class _AddOnBox extends StatelessWidget { children: [ Expanded( child: FlowyText( - billingInfo, + recommend, color: AFThemeExtension.of(context).secondaryTextColor, fontSize: 11, maxLines: 2, diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 20ede978d8..5232879284 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -172,7 +172,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -192,7 +192,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -207,7 +207,7 @@ dependencies = [ [[package]] name = "appflowy-local-ai" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "appflowy-plugin", @@ -226,7 +226,7 @@ dependencies = [ [[package]] name = "appflowy-plugin" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "cfg-if", @@ -826,7 +826,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "again", "anyhow", @@ -876,7 +876,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "collab-entity", "collab-rt-entity", @@ -888,7 +888,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "futures-channel", "futures-util", @@ -994,7 +994,7 @@ dependencies = [ "collab", "collab-entity", "collab-plugins", - "dashmap", + "dashmap 5.5.3", "getrandom 0.2.10", "js-sys", "lazy_static", @@ -1132,7 +1132,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "async-trait", @@ -1421,7 +1421,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa 1.0.6", - "phf 0.8.0", + "phf 0.11.2", "smallvec", ] @@ -1523,6 +1523,20 @@ dependencies = [ "parking_lot_core 0.9.8", ] +[[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.8", +] + [[package]] name = "data-encoding" version = "2.4.0" @@ -1532,7 +1546,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -1960,13 +1974,14 @@ dependencies = [ "appflowy-plugin", "base64 0.21.5", "bytes", - "dashmap", + "dashmap 6.0.1", "flowy-ai-pub", "flowy-codegen", "flowy-derive", "flowy-error", "flowy-notification", "flowy-sqlite", + "flowy-storage-pub", "futures", "futures-util", "lib-dispatch", @@ -2131,7 +2146,7 @@ dependencies = [ "collab-integrate", "collab-plugins", "csv", - "dashmap", + "dashmap 6.0.1", "fancy-regex 0.11.0", "flowy-codegen", "flowy-database-pub", @@ -2181,7 +2196,7 @@ dependencies = [ name = "flowy-derive" version = "0.1.0" dependencies = [ - "dashmap", + "dashmap 6.0.1", "flowy-ast", "flowy-codegen", "lazy_static", @@ -2203,7 +2218,7 @@ dependencies = [ "collab-entity", "collab-integrate", "collab-plugins", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-document-pub", @@ -2340,7 +2355,7 @@ name = "flowy-notification" version = "0.1.0" dependencies = [ "bytes", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "lazy_static", @@ -2483,6 +2498,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-error", @@ -2515,6 +2531,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tracing", ] [[package]] @@ -3051,7 +3068,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "futures-util", @@ -3068,7 +3085,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -3500,7 +3517,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -6098,7 +6115,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index 026dd6daf6..e1ea6f63ba 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -53,7 +53,7 @@ collab-user = { version = "0.2" } # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7878a018a18553e3d8201e572a0c066c14ba3b35" } [dependencies] serde_json.workspace = true @@ -128,5 +128,5 @@ collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy- # To update the commit ID, run: # scripts/tool/update_local_ai_rev.sh new_rev_id # ⚠️⚠️⚠️️ -appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } +appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } +appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock index e940bdd58a..d09880059e 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.lock +++ b/frontend/appflowy_web_app/src-tauri/Cargo.lock @@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "appflowy-local-ai" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "appflowy-plugin", @@ -217,7 +217,7 @@ dependencies = [ [[package]] name = "appflowy-plugin" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "cfg-if", @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "again", "anyhow", @@ -850,7 +850,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "collab-entity", "collab-rt-entity", @@ -862,7 +862,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "futures-channel", "futures-util", @@ -977,7 +977,7 @@ dependencies = [ "collab", "collab-entity", "collab-plugins", - "dashmap", + "dashmap 5.5.3", "getrandom 0.2.12", "js-sys", "lazy_static", @@ -1115,7 +1115,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -1140,7 +1140,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "async-trait", @@ -1411,7 +1411,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa 1.0.10", - "phf 0.8.0", + "phf 0.11.2", "smallvec", ] @@ -1513,6 +1513,20 @@ dependencies = [ "parking_lot_core 0.9.9", ] +[[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.9", +] + [[package]] name = "data-encoding" version = "2.5.0" @@ -1522,7 +1536,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -1990,13 +2004,14 @@ dependencies = [ "appflowy-plugin", "base64 0.21.7", "bytes", - "dashmap", + "dashmap 6.0.1", "flowy-ai-pub", "flowy-codegen", "flowy-derive", "flowy-error", "flowy-notification", "flowy-sqlite", + "flowy-storage-pub", "futures", "futures-util", "lib-dispatch", @@ -2161,7 +2176,7 @@ dependencies = [ "collab-integrate", "collab-plugins", "csv", - "dashmap", + "dashmap 6.0.1", "fancy-regex 0.11.0", "flowy-codegen", "flowy-database-pub", @@ -2211,7 +2226,7 @@ dependencies = [ name = "flowy-derive" version = "0.1.0" dependencies = [ - "dashmap", + "dashmap 6.0.1", "flowy-ast", "flowy-codegen", "lazy_static", @@ -2233,7 +2248,7 @@ dependencies = [ "collab-entity", "collab-integrate", "collab-plugins", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-document-pub", @@ -2370,7 +2385,7 @@ name = "flowy-notification" version = "0.1.0" dependencies = [ "bytes", - "dashmap", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "lazy_static", @@ -2513,6 +2528,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "dashmap 6.0.1", "flowy-codegen", "flowy-derive", "flowy-error", @@ -2545,6 +2561,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tracing", ] [[package]] @@ -3118,7 +3135,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "futures-util", @@ -3135,7 +3152,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -3572,7 +3589,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -6162,7 +6179,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml index e2b61d6d39..b4db66e726 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.toml +++ b/frontend/appflowy_web_app/src-tauri/Cargo.toml @@ -52,7 +52,7 @@ collab-user = { version = "0.2" } # Run the script: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7878a018a18553e3d8201e572a0c066c14ba3b35" } [dependencies] serde_json.workspace = true @@ -128,6 +128,6 @@ collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy- # To update the commit ID, run: # scripts/tool/update_local_ai_rev.sh new_rev_id # ⚠️⚠️⚠️️ -appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } +appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } +appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index b1a3ebe11a..5994543413 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -666,6 +666,7 @@ "downloadLLMPrompt": "Download {}", "downloadAppFlowyOfflineAI": "Downloading AI offline package will enable AI to run on your device. Do you want to continue?", "downloadLLMPromptDetail": "Downloading {} local model will take up to {} of storage. Do you want to continue?", + "downloadBigFilePrompt": "It may take around 10 minutes to complete the download", "downloadAIModelButton": "Download", "downloadingModel": "Downloading", "localAILoaded": "Local AI Model successfully added and ready to use", @@ -735,15 +736,15 @@ "title": "AI Max", "description": "Unlimited AI responses powered by GPT-4o, Claude 3.5 Sonnet, and more", "price": "{}", - "priceInfo": "per user per month", - "billingInfo": "billed annually or {} billed monthly" + "priceInfo": "per user per month billed annually", + "recommend": "" }, "aiOnDevice": { "title": "AI On-device for Mac", "description": "Run Mistral 7B, LLAMA 3, and more local models on your machine", "price": "{}", - "priceInfo": "per user per month", - "billingInfo": "billed annually or {} billed monthly" + "priceInfo": "per user per month billed annually", + "recommend": "Recommend M1 or newer" } }, "deal": { diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 9ff90f08e9..0785d7c68a 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -163,7 +163,7 @@ checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "app-error" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -183,7 +183,7 @@ dependencies = [ [[package]] name = "appflowy-ai-client" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -198,7 +198,7 @@ dependencies = [ [[package]] name = "appflowy-local-ai" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "appflowy-plugin", @@ -217,7 +217,7 @@ dependencies = [ [[package]] name = "appflowy-plugin" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" dependencies = [ "anyhow", "cfg-if", @@ -718,7 +718,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "again", "anyhow", @@ -768,7 +768,7 @@ dependencies = [ [[package]] name = "client-api-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "collab-entity", "collab-rt-entity", @@ -780,7 +780,7 @@ dependencies = [ [[package]] name = "client-websocket" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "futures-channel", "futures-util", @@ -993,7 +993,7 @@ dependencies = [ [[package]] name = "collab-rt-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bincode", @@ -1018,7 +1018,7 @@ dependencies = [ [[package]] name = "collab-rt-protocol" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "async-trait", @@ -1370,7 +1370,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -2747,7 +2747,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "futures-util", @@ -2764,7 +2764,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "bytes", @@ -5338,7 +5338,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6a44490daccb101c8b855443d2d6ded0fb752016#6a44490daccb101c8b855443d2d6ded0fb752016" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=7878a018a18553e3d8201e572a0c066c14ba3b35#7878a018a18553e3d8201e572a0c066c14ba3b35" dependencies = [ "anyhow", "app-error", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 54b2f1a187..1675bf3d7b 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -100,8 +100,8 @@ dashmap = "6.0.1" # Run the script.add_workspace_members: # scripts/tool/update_client_api_rev.sh new_rev_id # ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } -client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6a44490daccb101c8b855443d2d6ded0fb752016" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7878a018a18553e3d8201e572a0c066c14ba3b35" } +client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "7878a018a18553e3d8201e572a0c066c14ba3b35" } [profile.dev] opt-level = 0 @@ -148,5 +148,5 @@ collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy- # To update the commit ID, run: # scripts/tool/update_local_ai_rev.sh new_rev_id # ⚠️⚠️⚠️️ -appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "65802795ad8778de11c45b5af65d05c973709613" } +appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } +appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } diff --git a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs index e29ddae174..30f29d6c7f 100644 --- a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs @@ -12,6 +12,8 @@ use flowy_error::FlowyError; use futures::stream::BoxStream; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; +use serde_json::Value; +use std::collections::HashMap; use std::path::Path; pub type ChatMessageStream = BoxStream<'static, Result>; @@ -85,6 +87,7 @@ pub trait ChatCloudService: Send + Sync + 'static { workspace_id: &str, file_path: &Path, chat_id: &str, + metadata: Option>, ) -> Result<(), FlowyError>; async fn get_local_ai_config(&self, workspace_id: &str) -> Result; diff --git a/frontend/rust-lib/flowy-ai/src/chat.rs b/frontend/rust-lib/flowy-ai/src/chat.rs index b0fd1c42d3..667e072ea2 100644 --- a/frontend/rust-lib/flowy-ai/src/chat.rs +++ b/frontend/rust-lib/flowy-ai/src/chat.rs @@ -513,6 +513,7 @@ impl Chat { &self.user_service.workspace_id()?, &file_path, &self.chat_id, + None, ) .await?; diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs index 8ea17c1638..cf69120d80 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_chat.rs @@ -357,6 +357,7 @@ impl LocalAIController { } let mut index_metadata = HashMap::new(); + index_metadata.insert("id".to_string(), json!(&metadata.id)); index_metadata.insert("name".to_string(), json!(&metadata.name)); index_metadata.insert("at_name".to_string(), json!(format!("@{}", &metadata.name))); index_metadata.insert("source".to_string(), json!(&metadata.source)); diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs index 2b51ef7a61..5528a00ac5 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs @@ -359,6 +359,10 @@ impl LocalAIResourceController { let cloned_model_name = model_name.clone(); let progress = Arc::new(move |downloaded, total_size| { let progress = (downloaded as f64 / total_size as f64).clamp(0.0, 1.0); + if plugin_progress_tx.receiver_count() == 0 { + return; + } + if let Err(err) = plugin_progress_tx.send(format!("{}:progress:{}", cloned_model_name, progress)) { diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/stream_util.rs b/frontend/rust-lib/flowy-ai/src/local_ai/stream_util.rs index 5f411a9130..76eb01ea6f 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/stream_util.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/stream_util.rs @@ -1,39 +1,62 @@ use appflowy_plugin::error::PluginError; -use bytes::Bytes; + use flowy_ai_pub::cloud::QuestionStreamValue; use flowy_error::FlowyError; use futures::{ready, Stream}; use pin_project::pin_project; +use serde_json::Value; use std::pin::Pin; use std::task::{Context, Poll}; +use tracing::error; + +pub const STEAM_METADATA_KEY: &str = "0"; +pub const STEAM_ANSWER_KEY: &str = "1"; #[pin_project] -pub struct LocalAIStreamAdaptor { - stream: Pin> + Send>>, +pub struct QuestionStream { + stream: Pin> + Send>>, buffer: Vec, } -impl LocalAIStreamAdaptor { +impl QuestionStream { pub fn new(stream: S) -> Self where - S: Stream> + Send + 'static, + S: Stream> + Send + 'static, { - LocalAIStreamAdaptor { + QuestionStream { stream: Box::pin(stream), buffer: Vec::new(), } } } -impl Stream for LocalAIStreamAdaptor { +impl Stream for QuestionStream { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); + match ready!(this.stream.as_mut().poll_next(cx)) { - Some(Ok(bytes)) => match String::from_utf8(bytes.to_vec()) { - Ok(s) => Poll::Ready(Some(Ok(QuestionStreamValue::Answer { value: s }))), - Err(err) => Poll::Ready(Some(Err(FlowyError::internal().with_context(err)))), + Some(Ok(value)) => match value { + Value::Object(mut value) => { + if let Some(metadata) = value.remove(STEAM_METADATA_KEY) { + return Poll::Ready(Some(Ok(QuestionStreamValue::Metadata { value: metadata }))); + } + + if let Some(answer) = value + .remove(STEAM_ANSWER_KEY) + .and_then(|s| s.as_str().map(ToString::to_string)) + { + return Poll::Ready(Some(Ok(QuestionStreamValue::Answer { value: answer }))); + } + + error!("Invalid streaming value: {:?}", value); + Poll::Ready(None) + }, + _ => { + error!("Unexpected JSON value type: {:?}", value); + Poll::Ready(None) + }, }, Some(Err(err)) => Poll::Ready(Some(Err(FlowyError::local_ai().with_context(err)))), None => Poll::Ready(None), diff --git a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs index 2e7f4e54a2..598d5716c6 100644 --- a/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs +++ b/frontend/rust-lib/flowy-ai/src/middleware/chat_service_mw.rs @@ -4,6 +4,7 @@ use crate::local_ai::local_llm_chat::LocalAIController; use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY}; use crate::persistence::{select_single_message, ChatMessageTable}; use appflowy_plugin::error::PluginError; +use std::collections::HashMap; use flowy_ai_pub::cloud::{ ChatCloudService, ChatMessage, ChatMessageMetadata, ChatMessageType, CompletionType, @@ -15,11 +16,11 @@ use futures::{stream, Sink, StreamExt, TryStreamExt}; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; -use crate::local_ai::stream_util::LocalAIStreamAdaptor; +use crate::local_ai::stream_util::QuestionStream; use crate::stream_message::StreamMessage; use flowy_storage_pub::storage::StorageService; use futures_util::SinkExt; -use serde_json::json; +use serde_json::{json, Value}; use std::path::Path; use std::sync::{Arc, Weak}; use tracing::trace; @@ -157,7 +158,7 @@ impl ChatCloudService for AICloudServiceMiddleware { .stream_question(chat_id, &row.content, json!([])) .await { - Ok(stream) => Ok(LocalAIStreamAdaptor::new(stream).boxed()), + Ok(stream) => Ok(QuestionStream::new(stream).boxed()), Err(err) => { self.handle_plugin_error(err); Ok(stream::once(async { Err(FlowyError::local_ai_unavailable()) }).boxed()) @@ -284,18 +285,19 @@ impl ChatCloudService for AICloudServiceMiddleware { workspace_id: &str, file_path: &Path, chat_id: &str, + metadata: Option>, ) -> Result<(), FlowyError> { if self.local_llm_controller.is_running() { self .local_llm_controller - .index_file(chat_id, Some(file_path.to_path_buf()), None, None) + .index_file(chat_id, Some(file_path.to_path_buf()), None, metadata) .await .map_err(|err| FlowyError::local_ai().with_context(err))?; Ok(()) } else { self .cloud_service - .index_file(workspace_id, file_path, chat_id) + .index_file(workspace_id, file_path, chat_id, metadata) .await } } diff --git a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs index afb482d64a..f184b5a020 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -1,5 +1,6 @@ use client_api::entity::search_dto::SearchDocumentResponseItem; use flowy_search_pub::cloud::SearchCloudService; +use std::collections::HashMap; use std::path::Path; use std::sync::Arc; @@ -12,6 +13,7 @@ use collab::core::origin::{CollabClient, CollabOrigin}; use collab::preclude::CollabPlugin; use collab_entity::CollabType; use collab_plugins::cloud_storage::postgres::SupabaseDBPlugin; +use serde_json::Value; use tokio_stream::wrappers::WatchStream; use tracing::{debug, info}; @@ -719,11 +721,12 @@ impl ChatCloudService for ServerProvider { workspace_id: &str, file_path: &Path, chat_id: &str, + metadata: Option>, ) -> Result<(), FlowyError> { self .get_server()? .chat_service() - .index_file(workspace_id, file_path, chat_id) + .index_file(workspace_id, file_path, chat_id, metadata) .await } diff --git a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs index 3bdaf7e1a6..2e8e1a8eab 100644 --- a/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs +++ b/frontend/rust-lib/flowy-server/src/af_cloud/impls/chat.rs @@ -15,7 +15,8 @@ use futures_util::{StreamExt, TryStreamExt}; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; use lib_infra::util::{get_operating_system, OperatingSystem}; -use serde_json::json; +use serde_json::{json, Value}; +use std::collections::HashMap; use std::path::Path; pub(crate) struct AFCloudChatCloudServiceImpl { @@ -182,6 +183,7 @@ where _workspace_id: &str, _file_path: &Path, _chat_id: &str, + _metadata: Option>, ) -> Result<(), FlowyError> { return Err( FlowyError::not_support() diff --git a/frontend/rust-lib/flowy-server/src/default_impl.rs b/frontend/rust-lib/flowy-server/src/default_impl.rs index be0ce1092e..0e22a6313f 100644 --- a/frontend/rust-lib/flowy-server/src/default_impl.rs +++ b/frontend/rust-lib/flowy-server/src/default_impl.rs @@ -6,6 +6,8 @@ use flowy_ai_pub::cloud::{ use flowy_error::FlowyError; use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; +use serde_json::Value; +use std::collections::HashMap; use std::path::Path; pub(crate) struct DefaultChatCloudServiceImpl; @@ -96,6 +98,7 @@ impl ChatCloudService for DefaultChatCloudServiceImpl { _workspace_id: &str, _file_path: &Path, _chat_id: &str, + _metadata: Option>, ) -> Result<(), FlowyError> { Err(FlowyError::not_support().with_context("indexing file is not supported in local server.")) } From 14b60fb9b03d923839de85b055117db0cb11e469 Mon Sep 17 00:00:00 2001 From: Mohammad Zolfaghari Date: Mon, 12 Aug 2024 11:16:45 +0330 Subject: [PATCH 15/28] fix: list paste bug #5846 (#5917) * fix: list paste bug #5846 selecting a single word on a bullet or number list and pasting it from the clipboard would clear the whole text of that node and replace it with clipboard text. resolves #5846 * chore: update editor version --------- Co-authored-by: Lucas.Xu --- .../document_copy_and_paste_test.dart | 38 ++++ .../copy_and_paste/custom_cut_command.dart | 3 +- .../copy_and_paste/custom_paste_command.dart | 3 +- .../editor_state_paste_node_extension.dart | 188 ------------------ .../copy_and_paste/paste_from_html.dart | 3 +- .../paste_from_in_app_json.dart | 3 +- .../copy_and_paste/paste_from_plain_text.dart | 3 +- frontend/appflowy_flutter/pubspec.lock | 4 +- frontend/appflowy_flutter/pubspec.yaml | 2 +- 9 files changed, 46 insertions(+), 201 deletions(-) delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart index 9fe8d37070..457934dff4 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart @@ -165,6 +165,44 @@ void main() { }); }); + testWidgets('paste text on part of bullet list', (tester) async { + const plainText = 'test'; + + await tester.pasteContent( + plainText: plainText, + beforeTest: (editorState) async { + final transaction = editorState.transaction; + transaction.insertNodes( + [0], + [ + Node( + type: BulletedListBlockKeys.type, + attributes: { + 'delta': [ + {"insert": "bullet list"}, + ], + }, + ), + ], + ); + + // Set the selection to the second numbered list node (which has empty delta) + transaction.afterSelection = Selection( + start: Position(path: [0], offset: 7), + end: Position(path: [0], offset: 11), + ); + + await editorState.apply(transaction); + await tester.pumpAndSettle(); + }, + (editorState) { + final node = editorState.getNodeAtPath([0]); + expect(node?.delta?.toPlainText(), 'bullet test'); + expect(node?.type, BulletedListBlockKeys.type); + }, + ); + }); + testWidgets('paste image(png) from memory', (tester) async { final image = await rootBundle.load('assets/test/images/sample.png'); final bytes = image.buffer.asUint8List(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart index db83e8e5f6..a2f4442bd0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart @@ -1,6 +1,5 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; /// cut. diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart index 85b290863e..067d6b766c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart @@ -1,13 +1,12 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log, EditorCopyPaste; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart deleted file mode 100644 index 6cee6f1db7..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart +++ /dev/null @@ -1,188 +0,0 @@ -import 'package:appflowy_editor/appflowy_editor.dart'; - -final _listTypes = [ - BulletedListBlockKeys.type, - TodoListBlockKeys.type, - NumberedListBlockKeys.type, -]; - -extension PasteNodes on EditorState { - Future pasteSingleLineNode(Node insertedNode) async { - final selection = await deleteSelectionIfNeeded(); - if (selection == null) { - return; - } - final node = getNodeAtPath(selection.start.path); - final delta = node?.delta; - if (node == null || delta == null) { - return; - } - final transaction = this.transaction; - final insertedDelta = insertedNode.delta; - // if the node is empty and its type is paragprah, replace it with the inserted node. - if (delta.isEmpty && node.type == ParagraphBlockKeys.type) { - transaction.insertNode( - selection.end.path.next, - insertedNode, - ); - transaction.deleteNode(node); - final path = calculatePath(selection.end.path, [insertedNode]); - final offset = calculateLength([insertedNode]); - transaction.afterSelection = Selection.collapsed( - Position( - path: path, - offset: offset, - ), - ); - } else if (_listTypes.contains(node.type)) { - final convertedNode = insertedNode.copyWith(type: node.type); - final path = selection.start.path; - transaction - ..insertNode(path, convertedNode) - ..deleteNodesAtPath(path); - - // Set the afterSelection to the last child of the inserted node - final lastChildPath = calculatePath(path, [convertedNode]); - final lastChildOffset = calculateLength([convertedNode]); - transaction.afterSelection = Selection.collapsed( - Position(path: lastChildPath, offset: lastChildOffset), - ); - } else if (insertedDelta != null) { - // if the node is not empty, insert the delta from inserted node after the selection. - transaction.insertTextDelta(node, selection.endIndex, insertedDelta); - } - await apply(transaction); - } - - Future pasteMultiLineNodes(List nodes) async { - assert(nodes.length > 1); - - final selection = await deleteSelectionIfNeeded(); - if (selection == null) { - return; - } - final node = getNodeAtPath(selection.start.path); - final delta = node?.delta; - if (node == null || delta == null) { - return; - } - final transaction = this.transaction; - - final lastNodeLength = calculateLength(nodes); - // merge the current selected node delta into the nodes. - if (delta.isNotEmpty) { - nodes.first.insertDelta( - delta.slice(0, selection.startIndex), - insertAfter: false, - ); - - nodes.last.insertDelta( - delta.slice(selection.endIndex), - ); - } - - if (delta.isEmpty && node.type != ParagraphBlockKeys.type) { - nodes[0] = nodes.first.copyWith( - type: node.type, - attributes: { - ...node.attributes, - ...nodes.first.attributes, - }, - ); - } - - for (final child in node.children) { - nodes.last.insert(child); - } - - transaction.insertNodes(selection.end.path, nodes); - - // delete the current node. - transaction.deleteNode(node); - - final path = calculatePath(selection.start.path, nodes); - transaction.afterSelection = Selection.collapsed( - Position( - path: path, - offset: lastNodeLength, - ), - ); - - await apply(transaction); - } - - // delete the selection if it's not collapsed. - Future deleteSelectionIfNeeded() async { - final selection = this.selection; - if (selection == null) { - return null; - } - - // delete the selection first. - if (!selection.isCollapsed) { - await deleteSelection(selection); - } - - // fetch selection again.selection = editorState.selection; - assert(this.selection?.isCollapsed == true); - return this.selection; - } - - Path calculatePath(Path start, List nodes) { - var path = start; - for (var i = 0; i < nodes.length; i++) { - path = path.next; - } - path = path.previous; - if (nodes.last.children.isNotEmpty) { - return [ - ...path, - ...calculatePath([0], nodes.last.children.toList()), - ]; - } - return path; - } - - int calculateLength(List nodes) { - if (nodes.last.children.isNotEmpty) { - return calculateLength(nodes.last.children.toList()); - } - return nodes.last.delta?.length ?? 0; - } -} - -extension on Node { - void insertDelta(Delta delta, {bool insertAfter = true}) { - assert(delta.every((element) => element is TextInsert)); - if (this.delta == null) { - updateAttributes({ - blockComponentDelta: delta.toJson(), - }); - } else if (insertAfter) { - updateAttributes( - { - blockComponentDelta: this - .delta! - .compose( - Delta() - ..retain(this.delta!.length) - ..addAll(delta), - ) - .toJson(), - }, - ); - } else { - updateAttributes( - { - blockComponentDelta: delta - .compose( - Delta() - ..retain(delta.length) - ..addAll(this.delta!), - ) - .toJson(), - }, - ); - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart index 2047e6dd47..87259d5981 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart @@ -1,5 +1,4 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; +import 'package:appflowy_editor/appflowy_editor.dart'; extension PasteFromHtml on EditorState { Future pasteHtml(String html) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart index 8e425aeab6..00a2a6c2f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart @@ -1,8 +1,7 @@ import 'dart:convert'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide Log, EditorCopyPaste; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; extension PasteFromInAppJson on EditorState { Future pasteInAppJson(String inAppJson) async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart index 6100a0056d..b09c8c0dc4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart @@ -1,6 +1,5 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart'; import 'package:appflowy/shared/patterns/common_patterns.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' hide EditorCopyPaste; +import 'package:appflowy_editor/appflowy_editor.dart'; extension PasteFromPlainText on EditorState { Future pastePlainText(String plainText) async { diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 21b0791d8d..9b6b2102ef 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: dc10742 - resolved-ref: dc10742ba559e445e2ba1bd1b295cbf4758ccf3d + ref: "9d3e854" + resolved-ref: "9d3e854f11fd9d732535ce5f5b1c8f41517479a1" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "3.1.0" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 0389146a42..896bb4c73d 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -199,7 +199,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "dc10742" + ref: "9d3e854" appflowy_editor_plugins: git: From a29b170b1323229836e1eb06f1ab2e9676311b7f Mon Sep 17 00:00:00 2001 From: Annie Date: Mon, 12 Aug 2024 15:46:56 +0800 Subject: [PATCH 16/28] chore: remove stability ai (#5927) * chore: remove stability ai * chore: remove stabilityAI widgets --------- Co-authored-by: Lucas.Xu --- .../image/image_placeholder.dart | 14 +- .../multi_image_menu.dart | 7 +- .../multi_image_placeholder.dart | 31 ++--- .../upload_image_menu/upload_image_menu.dart | 50 +------- .../widgets/open_ai_image_widget.dart | 105 --------------- .../widgets/stability_ai_image_widget.dart | 120 ------------------ .../stability_ai/stability_ai_client.dart | 95 -------------- .../stability_ai/stability_ai_error.dart | 10 -- .../lib/startup/deps_resolver.dart | 19 --- frontend/resources/translations/en.json | 4 +- 10 files changed, 29 insertions(+), 426 deletions(-) delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/open_ai_image_widget.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart delete mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart index 6e3941a584..08ad0a9ac9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart @@ -102,7 +102,6 @@ class ImagePlaceholderState extends State { UploadImageType.local, UploadImageType.url, UploadImageType.unsplash, - UploadImageType.stabilityAI, ], onSelectedLocalImages: (paths) { controller.close(); @@ -188,12 +187,13 @@ class ImagePlaceholderState extends State { return [ Flexible( child: FlowyText( - PlatformExtension.isDesktop - ? isDraggingFiles - ? LocaleKeys.document_plugins_image_dropImageToInsert.tr() - : LocaleKeys.document_plugins_image_addAnImageDesktop.tr() - : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), - color: Theme.of(context).hintColor,), + PlatformExtension.isDesktop + ? isDraggingFiles + ? LocaleKeys.document_plugins_image_dropImageToInsert.tr() + : LocaleKeys.document_plugins_image_addAnImageDesktop.tr() + : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), + color: Theme.of(context).hintColor, + ), ), ]; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart index d419446328..a8534b6fd3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -25,6 +22,8 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:path/path.dart' as p; import 'package:provider/provider.dart'; @@ -129,7 +128,7 @@ class _MultiImageMenuState extends State { UploadImageType.local, UploadImageType.url, UploadImageType.unsplash, - UploadImageType.stabilityAI, + ], onSelectedLocalImages: insertLocalImages, onSelectedAIImage: insertAIImage, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart index ba4c779928..4e1cfd1fcb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart @@ -1,9 +1,7 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_service.dart'; @@ -24,6 +22,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart'; import 'package:path/path.dart' as p; @@ -67,19 +66,22 @@ class MultiImagePlaceholderState extends State { padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12), child: Row( children: [ - FlowySvg(FlowySvgs.slash_menu_icon_photo_gallery_s, - color: Theme.of(context).hintColor, - size: const Size.square(24),), + FlowySvg( + FlowySvgs.slash_menu_icon_photo_gallery_s, + color: Theme.of(context).hintColor, + size: const Size.square(24), + ), const HSpace(10), FlowyText( - PlatformExtension.isDesktop - ? isDraggingFiles - ? LocaleKeys.document_plugins_image_dropImageToInsert - .tr() - : LocaleKeys.document_plugins_image_addAnImageDesktop - .tr() - : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), - color: Theme.of(context).hintColor,), + PlatformExtension.isDesktop + ? isDraggingFiles + ? LocaleKeys.document_plugins_image_dropImageToInsert + .tr() + : LocaleKeys.document_plugins_image_addAnImageDesktop + .tr() + : LocaleKeys.document_plugins_image_addAnImageMobile.tr(), + color: Theme.of(context).hintColor, + ), ], ), ), @@ -104,7 +106,6 @@ class MultiImagePlaceholderState extends State { UploadImageType.local, UploadImageType.url, UploadImageType.unsplash, - UploadImageType.stabilityAI, ], onSelectedLocalImages: (paths) { controller.close(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart index cdda330115..8091452288 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart @@ -1,17 +1,15 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart'; +//import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/upload_image_file_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide ColorOption; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'widgets/embed_image_url_widget.dart'; @@ -19,8 +17,6 @@ enum UploadImageType { local, url, unsplash, - stabilityAI, - // openAI, color; String get description { @@ -31,10 +27,6 @@ enum UploadImageType { return LocaleKeys.document_imageBlock_embedLink_label.tr(); case UploadImageType.unsplash: return LocaleKeys.document_imageBlock_unsplash_label.tr(); - // case UploadImageType.openAI: - // return LocaleKeys.document_imageBlock_ai_label.tr(); - case UploadImageType.stabilityAI: - return LocaleKeys.document_imageBlock_stability_ai_label.tr(); case UploadImageType.color: return LocaleKeys.document_plugins_cover_colors.tr(); } @@ -68,33 +60,12 @@ class UploadImageMenu extends StatefulWidget { class _UploadImageMenuState extends State { late final List values; int currentTabIndex = 0; - bool supportOpenAI = false; - bool supportStabilityAI = false; @override void initState() { super.initState(); values = widget.supportTypes; - UserBackendService.getCurrentUserProfile().then( - (value) { - final supportOpenAI = value.fold( - (s) => s.openaiKey.isNotEmpty, - (e) => false, - ); - final supportStabilityAI = value.fold( - (s) => s.stabilityAiKey.isNotEmpty, - (e) => false, - ); - if (supportOpenAI != this.supportOpenAI || - supportStabilityAI != this.supportStabilityAI) { - setState(() { - this.supportOpenAI = supportOpenAI; - this.supportStabilityAI = supportStabilityAI; - }); - } - }, - ); } @override @@ -196,23 +167,6 @@ class _UploadImageMenuState extends State { ), ), ); - case UploadImageType.stabilityAI: - return supportStabilityAI - ? Expanded( - child: Container( - padding: const EdgeInsets.all(8.0), - child: StabilityAIImageWidget( - onSelectImage: (url) => widget.onSelectedLocalImages([url]), - ), - ), - ) - : Padding( - padding: const EdgeInsets.all(8.0), - child: FlowyText( - LocaleKeys.document_imageBlock_pleaseInputYourStabilityAIKey - .tr(), - ), - ); case UploadImageType.color: final theme = Theme.of(context); final padding = PlatformExtension.isMobile diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/open_ai_image_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/open_ai_image_widget.dart deleted file mode 100644 index 9c48cf6a1b..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/open_ai_image_widget.dart +++ /dev/null @@ -1,105 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; - -class OpenAIImageWidget extends StatefulWidget { - const OpenAIImageWidget({ - super.key, - required this.onSelectNetworkImage, - }); - - final void Function(String url) onSelectNetworkImage; - - @override - State createState() => _OpenAIImageWidgetState(); -} - -class _OpenAIImageWidgetState extends State { - Future, AIError>>? future; - String query = ''; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: FlowyTextField( - hintText: LocaleKeys.document_imageBlock_ai_placeholder.tr(), - onChanged: (value) => query = value, - onEditingComplete: _search, - ), - ), - const HSpace(4.0), - FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.search_label.tr(), - ), - onTap: _search, - ), - ], - ), - const VSpace(12.0), - if (future != null) - Expanded( - child: FutureBuilder( - future: future, - builder: (context, value) { - final data = value.data; - if (!value.hasData || - value.connectionState != ConnectionState.done || - data == null) { - return const CircularProgressIndicator.adaptive(); - } - return data.fold( - (s) => GridView.count( - crossAxisCount: 3, - mainAxisSpacing: 16.0, - crossAxisSpacing: 10.0, - childAspectRatio: 4 / 3, - children: s - .map( - (e) => GestureDetector( - onTap: () => widget.onSelectNetworkImage(e), - child: Image.network(e), - ), - ) - .toList(), - ), - (e) => Center( - child: FlowyText( - e.message, - maxLines: 3, - textAlign: TextAlign.center, - ), - ), - ); - }, - ), - ), - ], - ); - } - - void _search() async { - final openAI = await getIt.getAsync(); - setState(() { - future = openAI.generateImage( - prompt: query, - n: 6, - ); - }); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart deleted file mode 100644 index 0d5d986d10..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/stability_ai_image_widget.dart +++ /dev/null @@ -1,120 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/uuid.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:path/path.dart' as p; -import 'package:path_provider/path_provider.dart'; - -class StabilityAIImageWidget extends StatefulWidget { - const StabilityAIImageWidget({ - super.key, - required this.onSelectImage, - }); - - final void Function(String url) onSelectImage; - - @override - State createState() => _StabilityAIImageWidgetState(); -} - -class _StabilityAIImageWidgetState extends State { - Future, StabilityAIRequestError>>? future; - String query = ''; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: FlowyTextField( - hintText: LocaleKeys - .document_imageBlock_stability_ai_placeholder - .tr(), - onChanged: (value) => query = value, - onEditingComplete: _search, - ), - ), - const HSpace(4.0), - FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.search_label.tr(), - ), - onTap: _search, - ), - ], - ), - const VSpace(12.0), - if (future != null) - Expanded( - child: FutureBuilder( - future: future, - builder: (context, value) { - final data = value.data; - if (!value.hasData || - value.connectionState != ConnectionState.done || - data == null) { - return const CircularProgressIndicator.adaptive(); - } - return data.fold( - (s) => GridView.count( - crossAxisCount: 3, - mainAxisSpacing: 16.0, - crossAxisSpacing: 10.0, - childAspectRatio: 4 / 3, - children: s.map( - (e) { - final base64Image = base64Decode(e); - return GestureDetector( - onTap: () async { - final tempDirectory = await getTemporaryDirectory(); - final path = p.join( - tempDirectory.path, - '${uuid()}.png', - ); - File(path).writeAsBytesSync(base64Image); - widget.onSelectImage(path); - }, - child: Image.memory(base64Image), - ); - }, - ).toList(), - ), - (e) => Center( - child: FlowyText( - e.message, - maxLines: 3, - textAlign: TextAlign.center, - ), - ), - ); - }, - ), - ), - ], - ); - } - - void _search() async { - final stabilityAI = await getIt.getAsync(); - setState(() { - future = stabilityAI.generateImage( - prompt: query, - n: 6, - ); - }); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart deleted file mode 100644 index c3cbd11c97..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:http/http.dart' as http; - -enum StabilityAIRequestType { - imageGenerations; - - Uri get uri { - switch (this) { - case StabilityAIRequestType.imageGenerations: - return Uri.parse( - 'https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image', - ); - } - } -} - -abstract class StabilityAIRepository { - /// Generate image from Stability AI - /// - /// [prompt] is the prompt text - /// [n] is the number of images to generate - /// - /// the return value is a list of base64 encoded images - Future, StabilityAIRequestError>> generateImage({ - required String prompt, - int n = 1, - }); -} - -class HttpStabilityAIRepository implements StabilityAIRepository { - const HttpStabilityAIRepository({ - required this.client, - required this.apiKey, - }); - - final http.Client client; - final String apiKey; - - Map get headers => { - 'Authorization': 'Bearer $apiKey', - 'Content-Type': 'application/json', - }; - - @override - Future, StabilityAIRequestError>> generateImage({ - required String prompt, - int n = 1, - }) async { - final parameters = { - 'text_prompts': [ - { - 'text': prompt, - } - ], - 'samples': n, - }; - - try { - final response = await client.post( - StabilityAIRequestType.imageGenerations.uri, - headers: headers, - body: json.encode(parameters), - ); - - final data = json.decode( - utf8.decode(response.bodyBytes), - ); - if (response.statusCode == 200) { - final artifacts = data['artifacts'] as List; - final base64Images = artifacts - .map( - (e) => e['base64'].toString(), - ) - .toList(); - return FlowyResult.success(base64Images); - } else { - return FlowyResult.failure( - StabilityAIRequestError( - data['message'].toString(), - ), - ); - } - } catch (error) { - return FlowyResult.failure( - StabilityAIRequestError( - error.toString(), - ), - ); - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart deleted file mode 100644 index c699237762..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_error.dart +++ /dev/null @@ -1,10 +0,0 @@ -class StabilityAIRequestError { - StabilityAIRequestError(this.message); - - final String message; - - @override - String toString() { - return 'StabilityAIRequestError{message: $message}'; - } -} diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index 3532bc8420..d19e0b3f7a 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -4,7 +4,6 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/stability_ai/stability_ai_client.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/shared/custom_image_cache_manager.dart'; @@ -43,7 +42,6 @@ import 'package:flowy_infra/file_picker/file_picker_impl.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:get_it/get_it.dart'; -import 'package:http/http.dart' as http; class DependencyResolver { static Future resolve( @@ -100,23 +98,6 @@ void _resolveCommonService( }, ); - getIt.registerFactoryAsync( - () async { - final result = await UserBackendService.getCurrentUserProfile(); - return result.fold( - (s) { - return HttpStabilityAIRepository( - client: http.Client(), - apiKey: s.stabilityAiKey, - ); - }, - (e) { - throw Exception('Failed to get user profile: ${e.msg}'); - }, - ); - }, - ); - getIt.registerFactory( () => ClipboardService(), ); diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 5994543413..605ee9bc61 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1159,7 +1159,6 @@ "tooltipSelectIcon": "Select icon", "selectAnIcon": "Select an icon", "pleaseInputYourOpenAIKey": "please input your AI key", - "pleaseInputYourStabilityAIKey": "please input your Stability AI key", "clickToLogout": "Click to logout the current user" }, "mobile": { @@ -1713,7 +1712,6 @@ }, "searchForAnImage": "Search for an image", "pleaseInputYourOpenAIKey": "please input your AI key in Settings page", - "pleaseInputYourStabilityAIKey": "please input your Stability AI key in Settings page", "saveImageToGallery": "Save image", "failedToAddImageToGallery": "Failed to add image to gallery", "successToAddImageToGallery": "Image added to gallery successfully", @@ -2411,4 +2409,4 @@ "commentAddedSuccessfully": "Comment added successfully.", "commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?" } -} \ No newline at end of file +} From 34465efc24104acc933126036e381548613ef7c6 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Mon, 12 Aug 2024 16:03:52 +0800 Subject: [PATCH 17/28] chore: replace all the buttons on settings page with the primary button style (#5937) --- .../lib/plugins/shared/share/_shared.dart | 24 ++------- .../lib/plugins/shared/share/publish_tab.dart | 13 +++-- .../menu/sidebar/space/shared_widget.dart | 2 +- .../settings/pages/settings_account_view.dart | 13 ++--- .../pages/settings_manage_data_view.dart | 23 ++++----- .../shared/single_setting_action.dart | 8 +-- .../settings/widgets/_restart_app_button.dart | 16 +++--- .../members/workspace_member_page.dart | 16 +++--- .../flowy_infra_ui/lib/flowy_infra_ui.dart | 1 + .../style_widget/primary_rounded_button.dart | 50 +++++++++++++++++++ 10 files changed, 93 insertions(+), 73 deletions(-) create mode 100644 frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart index 3073833e17..2228e9bc19 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart @@ -41,29 +41,11 @@ class ShareMenuButton extends StatelessWidget { tabs: tabs, ), ), - child: const _ShareButton(), + child: PrimaryRoundedButton( + text: LocaleKeys.shareAction_buttonText.tr(), + ), ), ), ); } } - -class _ShareButton extends StatelessWidget { - const _ShareButton(); - - @override - Widget build(BuildContext context) { - return FlowyButton( - text: FlowyText( - LocaleKeys.shareAction_buttonText.tr(), - fontSize: 14.0, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onPrimary, - ), - margin: const EdgeInsets.symmetric(horizontal: 14.0), - backgroundColor: Theme.of(context).colorScheme.primary, - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), - radius: BorderRadius.circular(10.0), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index 7fdbab05b9..d48ead7eaf 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -190,6 +190,7 @@ class _PublishedWidgetState extends State<_PublishedWidget> { title: LocaleKeys.shareAction_visitSite.tr(), borderRadius: const BorderRadius.all(Radius.circular(10)), fillColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), textColor: Theme.of(context).colorScheme.onPrimary, ); } @@ -258,13 +259,11 @@ class _PublishButton extends StatelessWidget { @override Widget build(BuildContext context) { - return RoundedTextButton( - height: 36, - title: LocaleKeys.shareAction_publish.tr(), - padding: const EdgeInsets.symmetric(vertical: 9.0), - fontSize: 14.0, - textColor: Theme.of(context).colorScheme.onPrimary, - onPressed: onPublish, + return PrimaryRoundedButton( + text: LocaleKeys.shareAction_publish.tr(), + useIntrinsicWidth: false, + margin: const EdgeInsets.symmetric(vertical: 9.0), + onTap: onPublish, ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index 8f97c59515..cda46128e7 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -213,7 +213,7 @@ class SpaceCancelOrConfirmButton extends StatelessWidget { radius: BorderRadius.circular(8), text: FlowyText.regular( confirmButtonName, - color: Colors.white, + color: Theme.of(context).colorScheme.onPrimary, ), onTap: onConfirm, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart index af31e19181..d2ac61e472 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart @@ -193,17 +193,14 @@ class SignInOutButton extends StatelessWidget { children: [ SizedBox( height: 48, - child: FlowyTextButton( - signIn + child: PrimaryRoundedButton( + text: signIn ? LocaleKeys.settings_accountPage_login_loginLabel.tr() : LocaleKeys.settings_accountPage_login_logoutLabel.tr(), - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), fontWeight: FontWeight.w600, - radius: BorderRadius.circular(12), - fillColor: Theme.of(context).colorScheme.primary, - hoverColor: const Color(0xFF005483), - fontHoverColor: Colors.white, - onPressed: () { + radius: 12.0, + onTap: () { if (signIn) { _showSignInDialog(context); } else { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart index 636a33a314..a4dd5b222f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart @@ -1,8 +1,5 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -25,10 +22,10 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -432,15 +429,13 @@ class _DataPathActions extends StatelessWidget { children: [ SizedBox( height: 42, - child: FlowyTextButton( - LocaleKeys.settings_manageDataPage_dataStorage_actions_change.tr(), - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: PrimaryRoundedButton( + text: LocaleKeys.settings_manageDataPage_dataStorage_actions_change + .tr(), + margin: const EdgeInsets.symmetric(horizontal: 24), fontWeight: FontWeight.w600, - radius: BorderRadius.circular(12), - fillColor: Theme.of(context).colorScheme.primary, - hoverColor: const Color(0xFF005483), - fontHoverColor: Colors.white, - onPressed: () async { + radius: 12.0, + onTap: () async { final path = await getIt().getDirectoryPath(); if (!context.mounted || path == null || currentPath == path) { return; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart index 646f712c3a..5f2ecce277 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart @@ -1,10 +1,9 @@ -import 'package:flutter/material.dart'; - import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; enum SingleSettingsButtonType { primary, @@ -108,6 +107,7 @@ class SingleSettingAction extends StatelessWidget { radius: Corners.s8Border, hoverColor: hoverColor(context), fontColor: fontColor(context), + textColor: fontColor(context), fontHoverColor: fontHoverColor(context), borderColor: borderColor(context), fontSize: 12, @@ -128,7 +128,7 @@ class SingleSettingAction extends StatelessWidget { Color? hoverColor(BuildContext context) { if (buttonType.isPrimary) { - return const Color(0xFF005483); + return Theme.of(context).colorScheme.primary.withOpacity(0.9); } if (buttonType.isHighlight) { @@ -146,7 +146,7 @@ class SingleSettingAction extends StatelessWidget { return const Color(0xFF5C3699); } - return null; + return Theme.of(context).colorScheme.onPrimary; } Color? fontHoverColor(BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/_restart_app_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/_restart_app_button.dart index 13c9e80bd9..744c042c62 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/_restart_app_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/_restart_app_button.dart @@ -1,10 +1,9 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart'; import 'package:appflowy_editor/appflowy_editor.dart' show PlatformExtension; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class RestartButton extends StatelessWidget { const RestartButton({ @@ -40,15 +39,12 @@ class RestartButton extends StatelessWidget { children: [ SizedBox( height: 42, - child: FlowyTextButton( - LocaleKeys.settings_menu_restartApp.tr(), - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + child: PrimaryRoundedButton( + text: LocaleKeys.settings_menu_restartApp.tr(), + margin: const EdgeInsets.symmetric(horizontal: 24), fontWeight: FontWeight.w600, - radius: BorderRadius.circular(12), - fillColor: Theme.of(context).colorScheme.primary, - hoverColor: const Color(0xFF005483), - fontHoverColor: Colors.white, - onPressed: onClick, + radius: 12.0, + onTap: onClick, ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart index 1cbe2fa1a4..8d61254008 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -17,7 +15,7 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:string_validator/string_validator.dart'; @@ -267,11 +265,13 @@ class _InviteMemberState extends State<_InviteMember> { SizedBox( height: 48.0, child: IntrinsicWidth( - child: RoundedTextButton( - title: LocaleKeys.settings_appearance_members_sendInvite.tr(), - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - onPressed: _inviteMember, + child: PrimaryRoundedButton( + text: LocaleKeys.settings_appearance_members_sendInvite.tr(), + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + onTap: _inviteMember, ), ), ), diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart index d8e8604386..e1f58189b1 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/flowy_infra_ui.dart @@ -15,6 +15,7 @@ export 'style_widget/button.dart'; export 'style_widget/color_picker.dart'; export 'style_widget/divider.dart'; export 'style_widget/icon_button.dart'; +export 'style_widget/primary_rounded_button.dart'; export 'style_widget/scrollbar.dart'; export 'style_widget/scrolling/styled_list.dart'; export 'style_widget/scrolling/styled_scroll_bar.dart'; diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart new file mode 100644 index 0000000000..687715c826 --- /dev/null +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart @@ -0,0 +1,50 @@ +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; + +class PrimaryRoundedButton extends StatelessWidget { + const PrimaryRoundedButton({ + super.key, + required this.text, + this.fontSize, + this.fontWeight, + this.color, + this.radius, + this.margin, + this.onTap, + this.hoverColor, + this.backgroundColor, + this.useIntrinsicWidth = true, + }); + + final String text; + final double? fontSize; + final FontWeight? fontWeight; + final Color? color; + final double? radius; + final EdgeInsets? margin; + final VoidCallback? onTap; + final Color? hoverColor; + final Color? backgroundColor; + final bool useIntrinsicWidth; + + @override + Widget build(BuildContext context) { + return FlowyButton( + useIntrinsicWidth: useIntrinsicWidth, + text: FlowyText( + text, + fontSize: fontSize ?? 14.0, + fontWeight: fontWeight ?? FontWeight.w500, + color: Theme.of(context).colorScheme.onPrimary, + textAlign: TextAlign.center, + ), + margin: margin ?? const EdgeInsets.symmetric(horizontal: 14.0), + backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.primary, + hoverColor: + hoverColor ?? Theme.of(context).colorScheme.primary.withOpacity(0.9), + radius: BorderRadius.circular(radius ?? 10.0), + onTap: onTap, + ); + } +} From 7e53b34484d3350ec02b7143ee29fef2ca0da1c1 Mon Sep 17 00:00:00 2001 From: Annie Date: Mon, 12 Aug 2024 16:19:16 +0800 Subject: [PATCH 18/28] chore: center confirm or cancel (#5936) Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com> --- .../home/menu/sidebar/space/shared_widget.dart | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index cda46128e7..992647bc08 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -195,7 +195,10 @@ class SpaceCancelOrConfirmButton extends StatelessWidget { child: FlowyButton( useIntrinsicWidth: true, margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 9.0), - text: FlowyText.regular(LocaleKeys.button_cancel.tr()), + text: FlowyText.regular( + LocaleKeys.button_cancel.tr(), + lineHeight: 1.0, + ), onTap: onCancel, ), ), @@ -213,6 +216,7 @@ class SpaceCancelOrConfirmButton extends StatelessWidget { radius: BorderRadius.circular(8), text: FlowyText.regular( confirmButtonName, + lineHeight: 1.0, color: Theme.of(context).colorScheme.onPrimary, ), onTap: onConfirm, @@ -253,6 +257,7 @@ class SpaceOkButton extends StatelessWidget { radius: BorderRadius.circular(8), text: FlowyText.regular( confirmButtonName, + lineHeight: 1.0, color: Colors.white, ), onTap: onConfirm, From 1db8480b7550c85c6c05f79d4faee762ea240100 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:28:34 +0800 Subject: [PATCH 19/28] chore: using write lock to fix acquire lock error (#5938) --- frontend/rust-lib/flowy-folder/src/manager.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index c5cfb2b85c..0f3ff02c1e 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -373,7 +373,7 @@ impl FolderManager { F1: FnOnce() -> Output, F2: FnOnce(&Folder) -> Output, { - let folder = self.mutex_folder.read(); + let folder = self.mutex_folder.write(); match &*folder { None => none_callback(), Some(folder) => f2(folder), From d3d9ab2fb0877e3c239e390f9c67bc29822f187d Mon Sep 17 00:00:00 2001 From: Annie Date: Mon, 12 Aug 2024 18:08:54 +0800 Subject: [PATCH 20/28] chore: reset line height to null as default value (#5942) --- .../presentation/message/ai_text_message.dart | 1 + .../editor_plugins/file/file_upload_menu.dart | 1 + .../pages/setting_ai_view/settings_ai_view.dart | 2 ++ .../settings/pages/settings_manage_data_view.dart | 1 + .../pages/settings_plan_comparison_dialog.dart | 1 + .../flowy_infra_ui/lib/style_widget/text.dart | 12 ++++++------ 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart index 7aa48b094f..4b711e4fda 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart @@ -60,6 +60,7 @@ class ChatAIMessageWidget extends StatelessWidget { onAIResponseLimit: () { return FlowyText( LocaleKeys.sideBar_askOwnerToUpgradeToAIMax.tr(), + lineHeight: 1.5, maxLines: 10, ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart index b819bb9869..a25e5c9b7c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart @@ -162,6 +162,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { .tr(), fontSize: 16, maxLines: 2, + lineHeight: 1.5, textAlign: TextAlign.center, ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart index a0b4ceacee..c5e160fb03 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart @@ -204,6 +204,7 @@ class _UpgradeToAILocalPlanState extends State<_UpgradeToAILocalPlan> { FlowyText.medium( LocaleKeys.sideBar_upgradeToAILocal.tr(), maxLines: 10, + lineHeight: 1.5, ), const VSpace(4), Opacity( @@ -212,6 +213,7 @@ class _UpgradeToAILocalPlanState extends State<_UpgradeToAILocalPlan> { LocaleKeys.sideBar_upgradeToAILocalDesc.tr(), fontSize: 12, maxLines: 10, + lineHeight: 1.5, ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart index a4dd5b222f..2aa1a40037 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart @@ -362,6 +362,7 @@ class _CurrentPathState extends State<_CurrentPath> { widget.path, maxLines: 2, overflow: TextOverflow.ellipsis, + lineHeight: 1.5, decoration: isHovering ? TextDecoration.underline : null, color: isLM ? const Color(0xFF005483) diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index 3334450c73..f2dec9550c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -626,6 +626,7 @@ class _Heading extends StatelessWidget { description!, fontSize: 12, maxLines: 5, + lineHeight: 1.5, ), ), ], diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 5da368870f..8260a19b3b 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -41,8 +41,8 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - // https://api.flutter.dev/flutter/painting/TextStyle/height.html - this.lineHeight = 1.5, + // // https://api.flutter.dev/flutter/painting/TextStyle/height.html + this.lineHeight, this.figmaLineHeight, this.withTooltip = false, this.isEmoji = false, @@ -61,7 +61,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1.5, + this.lineHeight, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -82,7 +82,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1.5, + this.lineHeight, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -102,7 +102,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1.5, + this.lineHeight, this.withTooltip = false, this.isEmoji = false, this.strutStyle, @@ -122,7 +122,7 @@ class FlowyText extends StatelessWidget { this.selectable = false, this.fontFamily, this.fallbackFontFamily, - this.lineHeight = 1.5, + this.lineHeight, this.withTooltip = false, this.isEmoji = false, this.strutStyle, From 6e26dc128cd401a0ec1f475a1443ad8f5619060f Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 12 Aug 2024 22:24:06 +0800 Subject: [PATCH 21/28] chore: fix message id (#5943) --- .../lib/plugins/ai_chat/application/chat_bloc.dart | 12 ++++++++++-- .../ai_chat/application/chat_user_message_bloc.dart | 2 +- frontend/rust-lib/flowy-folder/src/manager.rs | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart index 09bf21acaa..f1b6be106a 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart @@ -88,7 +88,13 @@ class ChatBloc extends Bloc { Int64? beforeMessageId; final oldestMessage = _getOlderstMessage(); if (oldestMessage != null) { - beforeMessageId = Int64.parseInt(oldestMessage.id); + try { + beforeMessageId = Int64.parseInt(oldestMessage.id); + } catch (e) { + Log.error( + "Failed to parse message id: $e, messaeg_id: ${oldestMessage.id}", + ); + } } _loadPrevMessage(beforeMessageId); emit( @@ -439,7 +445,9 @@ class ChatBloc extends Bloc { QuestionStream stream, Map? sentMetadata, ) { - questionStreamMessageId = nanoid(); + final now = DateTime.now(); + final timestamp = now.millisecondsSinceEpoch; + questionStreamMessageId = timestamp.toString(); final Map metadata = {}; // if (sentMetadata != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart index c0a29c2453..d6918eab53 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_user_message_bloc.dart @@ -15,7 +15,7 @@ class ChatUserMessageBloc ), ) { on( - (event, emit) async { + (event, emit) { event.when( initial: () { if (state.stream != null) { diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 0f3ff02c1e..f9034bf481 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -337,7 +337,7 @@ impl FolderManager { pub async fn get_workspace_pb(&self) -> FlowyResult { let workspace_id = self.user.workspace_id()?; - let guard = self.mutex_folder.read(); + let guard = self.mutex_folder.write(); let folder = guard .as_ref() .ok_or(FlowyError::internal().with_context("folder is not initialized"))?; From 4b710527c9cf8f55a994fb0f046abec12929899b Mon Sep 17 00:00:00 2001 From: Annie Date: Mon, 12 Aug 2024 22:24:24 +0800 Subject: [PATCH 22/28] chore: remove file upload limitation & add tooltip for search (#5944) --- .../file/file_block_component.dart | 8 ++- .../editor_plugins/file/file_upload_menu.dart | 18 ++++-- .../upload_image_menu/upload_image_menu.dart | 55 ++++++++++--------- .../home/menu/sidebar/sidebar.dart | 29 ++++++++-- frontend/resources/flowy_icons/16x/hide.svg | 6 +- .../resources/flowy_icons/16x/m_rename.svg | 15 ++++- frontend/resources/flowy_icons/16x/show.svg | 4 ++ frontend/resources/flowy_icons/24x/show.svg | 5 +- frontend/resources/translations/en.json | 5 +- 9 files changed, 96 insertions(+), 49 deletions(-) create mode 100644 frontend/resources/flowy_icons/16x/show.svg diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart index dc02b68503..5b1b3aa979 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart @@ -208,9 +208,11 @@ class FileBlockComponentState extends State child: Row( children: [ const HSpace(10), - FlowySvg(FlowySvgs.slash_menu_icon_file_s, - color: Theme.of(context).hintColor, - size: const Size.square(24),), + FlowySvg( + FlowySvgs.slash_menu_icon_file_s, + color: Theme.of(context).hintColor, + size: const Size.square(24), + ), const HSpace(10), ..._buildTrailing(context), ], diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart index a25e5c9b7c..4baa8506fe 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart @@ -142,7 +142,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { borderType: BorderType.RRect, color: isDragging ? Theme.of(context).colorScheme.primary - : Colors.black, + : Theme.of(context).hintColor, child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -153,7 +153,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { LocaleKeys.document_plugins_file_dropFileToUpload .tr(), fontSize: 16, - color: Theme.of(context).colorScheme.primary, + color: Theme.of(context).hintColor, ), const VSpace(13.5), ] else ...[ @@ -164,6 +164,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { maxLines: 2, lineHeight: 1.5, textAlign: TextAlign.center, + color: Theme.of(context).hintColor, ), ], ], @@ -208,6 +209,7 @@ class _FileUploadNetworkState extends State<_FileUploadNetwork> { alignment: Alignment.center, child: Column( children: [ + const VSpace(12), FlowyTextField( hintText: LocaleKeys.document_plugins_file_networkHint.tr(), onChanged: (value) => inputText = value, @@ -220,19 +222,25 @@ class _FileUploadNetworkState extends State<_FileUploadNetwork> { color: Theme.of(context).colorScheme.error, ), ], - const VSpace(8), + const VSpace(20), SizedBox( - width: 160, + height: 32, + width: 300, child: FlowyButton( + backgroundColor: Theme.of(context).colorScheme.primary, + hoverColor: + Theme.of(context).colorScheme.primary.withOpacity(0.9), showDefaultBoxDecorationOnMobile: true, - margin: const EdgeInsets.all(8.0), + margin: const EdgeInsets.all(5), text: FlowyText( LocaleKeys.document_plugins_file_networkAction.tr(), textAlign: TextAlign.center, + color: Theme.of(context).colorScheme.onPrimary, ), onTap: submit, ), ), + const VSpace(8), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart index 8091452288..70b54e0da1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart @@ -121,34 +121,37 @@ class _UploadImageMenuState extends State { final type = values[currentTabIndex]; switch (type) { case UploadImageType.local: - return Padding( - padding: const EdgeInsets.all(8.0), - child: Container( - alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: Theme.of(context).colorScheme.outline, + return Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Theme.of(context).colorScheme.outline, + ), + ), + constraints: constraints, + child: Column( + children: [ + UploadImageFileWidget( + allowMultipleImages: widget.allowMultipleImages, + onPickFiles: widget.onSelectedLocalImages, + ), + ], + ), ), ), - constraints: constraints, - child: Column( - children: [ - UploadImageFileWidget( - allowMultipleImages: widget.allowMultipleImages, - onPickFiles: widget.onSelectedLocalImages, - ), - if (widget.limitMaximumImageSize) ...[ - const VSpace(6.0), - FlowyText( - LocaleKeys.document_imageBlock_maximumImageSize.tr(), - fontSize: 12.0, - color: Theme.of(context).hintColor, - ), - ], - ], - ), - ), + // if (widget.limitMaximumImageSize) ...[ + // FlowyText( + // LocaleKeys.document_imageBlock_maximumImageSize.tr(), + // fontSize: 10.0, + // color: Theme.of(context).hintColor, + // ), + // ], + ], ); case UploadImageType.url: return Container( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart index f6c75ff9d1..da49268c53 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -455,12 +456,28 @@ class _SidebarSearchButton extends StatelessWidget { @override Widget build(BuildContext context) { - return FlowyButton( - onTap: () => CommandPalette.of(context).toggle(), - leftIcon: const FlowySvg(FlowySvgs.search_s), - iconPadding: 12.0, - margin: const EdgeInsets.only(left: 8.0), - text: FlowyText.regular(LocaleKeys.search_label.tr()), + return FlowyTooltip( + richMessage: TextSpan( + children: [ + TextSpan( + text: '${LocaleKeys.search_sidebarSearchIcon.tr()}\n', + style: context.tooltipTextStyle(), + ), + TextSpan( + text: Platform.isMacOS ? '⌘+P' : 'Ctrl+P', + style: context + .tooltipTextStyle() + ?.copyWith(color: Theme.of(context).hintColor), + ), + ], + ), + child: FlowyButton( + onTap: () => CommandPalette.of(context).toggle(), + leftIcon: const FlowySvg(FlowySvgs.search_s), + iconPadding: 12.0, + margin: const EdgeInsets.only(left: 8.0), + text: FlowyText.regular(LocaleKeys.search_label.tr()), + ), ); } } diff --git a/frontend/resources/flowy_icons/16x/hide.svg b/frontend/resources/flowy_icons/16x/hide.svg index 45e81d8748..d76e4576df 100644 --- a/frontend/resources/flowy_icons/16x/hide.svg +++ b/frontend/resources/flowy_icons/16x/hide.svg @@ -1,4 +1,6 @@ - - + + + + diff --git a/frontend/resources/flowy_icons/16x/m_rename.svg b/frontend/resources/flowy_icons/16x/m_rename.svg index 98200fd061..8f7620a806 100644 --- a/frontend/resources/flowy_icons/16x/m_rename.svg +++ b/frontend/resources/flowy_icons/16x/m_rename.svg @@ -1,4 +1,13 @@ - - - + + + + + + + + + + + + diff --git a/frontend/resources/flowy_icons/16x/show.svg b/frontend/resources/flowy_icons/16x/show.svg new file mode 100644 index 0000000000..1124aee221 --- /dev/null +++ b/frontend/resources/flowy_icons/16x/show.svg @@ -0,0 +1,4 @@ + + + + diff --git a/frontend/resources/flowy_icons/24x/show.svg b/frontend/resources/flowy_icons/24x/show.svg index 3550115093..0d411e5cc4 100644 --- a/frontend/resources/flowy_icons/24x/show.svg +++ b/frontend/resources/flowy_icons/24x/show.svg @@ -1,4 +1,5 @@ - - + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 605ee9bc61..58b2d7cc24 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1653,7 +1653,7 @@ "placeholderText": "Upload or embed a file", "placeholderDragging": "Drop the file to upload", "dropFileToUpload": "Drop the file to upload", - "fileUploadHint": "Drag and drop a file here\nor click to select a file.", + "fileUploadHint": "Drop a file here to upload\nor click to browse", "networkHint": "Paste a file link", "networkUrlInvalid": "Invalid URL, please correct the URL and try again", "networkAction": "Embed file link", @@ -1883,6 +1883,7 @@ }, "search": { "label": "Search", + "sidebarSearchIcon": "Search and quickly jump to a page", "placeholder": { "actions": "Search actions..." } @@ -2409,4 +2410,4 @@ "commentAddedSuccessfully": "Comment added successfully.", "commentAddedSuccessTip": "You've just added or replied to a comment. Would you like to jump to the top to see the latest comments?" } -} +} \ No newline at end of file From 93f9a2cab1ec6c594b0d1897d32a4a021db34999 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 13 Aug 2024 09:32:22 +0800 Subject: [PATCH 23/28] feat: display no access page (#5941) * feat: display no access page * fix: optimize the primary rounded button --- .../mention/mention_page_block.dart | 92 +++++++++++-------- .../lib/plugins/shared/share/_shared.dart | 1 + .../lib/plugins/shared/share/publish_tab.dart | 2 + .../workspace/_sidebar_workspace_menu.dart | 2 + .../shared/single_setting_action.dart | 1 + .../lib/style_widget/button.dart | 5 +- .../style_widget/primary_rounded_button.dart | 6 ++ frontend/resources/translations/en.json | 3 +- 8 files changed, 71 insertions(+), 41 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart index 6755be9690..34095e7512 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -21,6 +22,7 @@ import 'package:appflowy_editor/appflowy_editor.dart' TextTransaction, paragraphNode; import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter/material.dart'; @@ -105,57 +107,69 @@ class _MentionPageBlockState extends State { // memorize the result pageMemorizer[widget.pageId] = view; if (view == null) { - return const SizedBox.shrink(); - } - - final iconSize = widget.textStyle?.fontSize ?? 16.0; - final child = GestureDetector( - onTap: handleTap, - onDoubleTap: handleDoubleTap, - behavior: HitTestBehavior.translucent, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const HSpace(4), - view.icon.value.isNotEmpty - ? EmojiText( - emoji: view.icon.value, - fontSize: 12, - textAlign: TextAlign.center, - lineHeight: 1.3, - ) - : FlowySvg( - view.layout.icon, - size: Size.square(iconSize + 2.0), - ), - const HSpace(2), - FlowyText( - view.name, + return FlowyHover( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: FlowyText( + LocaleKeys.document_mention_noAccess.tr(), + color: Theme.of(context).disabledColor, decoration: TextDecoration.underline, fontSize: widget.textStyle?.fontSize, fontWeight: widget.textStyle?.fontWeight, ), - const HSpace(2), - ], - ), - ); - - if (PlatformExtension.isMobile) { - return child; + ), + ); } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 2), - child: FlowyHover( - cursor: SystemMouseCursors.click, - child: child, - ), + final iconSize = widget.textStyle?.fontSize ?? 16.0; + Widget child = Row( + mainAxisSize: MainAxisSize.min, + children: [ + const HSpace(4), + view.icon.value.isNotEmpty + ? EmojiText( + emoji: view.icon.value, + fontSize: 12, + textAlign: TextAlign.center, + lineHeight: 1.3, + ) + : FlowySvg( + view.layout.icon, + size: Size.square(iconSize + 2.0), + ), + const HSpace(2), + FlowyText( + view.name, + decoration: TextDecoration.underline, + fontSize: widget.textStyle?.fontSize, + fontWeight: widget.textStyle?.fontWeight, + ), + const HSpace(4), + ], + ); + + if (PlatformExtension.isDesktop) { + child = Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: FlowyHover( + cursor: SystemMouseCursors.click, + child: child, + ), + ); + } + + return GestureDetector( + onTap: handleTap, + onDoubleTap: PlatformExtension.isMobile ? handleDoubleTap : null, + behavior: HitTestBehavior.opaque, + child: child, ); }, ); } Future handleTap() async { + debugPrint('handleTap'); final view = await fetchView(widget.pageId); if (view == null) { Log.error('Page(${widget.pageId}) not found'); diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart index 2228e9bc19..23bcb88395 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/_shared.dart @@ -43,6 +43,7 @@ class ShareMenuButton extends StatelessWidget { ), child: PrimaryRoundedButton( text: LocaleKeys.shareAction_buttonText.tr(), + figmaLineHeight: 16, ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index d48ead7eaf..220cb92717 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -263,6 +263,8 @@ class _PublishButton extends StatelessWidget { text: LocaleKeys.shareAction_publish.tr(), useIntrinsicWidth: false, margin: const EdgeInsets.symmetric(vertical: 9.0), + fontSize: 14.0, + figmaLineHeight: 18.0, onTap: onPublish, ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart index a383caa082..97ee729e52 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart @@ -248,6 +248,7 @@ class _WorkspaceInfo extends StatelessWidget { FlowyText.medium( workspace.name, fontSize: 14.0, + figmaLineHeight: 17.0, overflow: TextOverflow.ellipsis, withTooltip: true, ), @@ -260,6 +261,7 @@ class _WorkspaceInfo extends StatelessWidget { members.length, ), fontSize: 10.0, + figmaLineHeight: 12.0, color: Theme.of(context).hintColor, ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart index 5f2ecce277..95fd067265 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart @@ -113,6 +113,7 @@ class SingleSettingAction extends StatelessWidget { fontSize: 12, isDangerous: buttonType.isDangerous, onPressed: onPressed, + lineHeight: 1.0, ), ), ], diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index fba47aa7d7..af5370dbfa 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -306,6 +306,7 @@ class FlowyTextButton extends StatelessWidget { this.fontFamily, this.isDangerous = false, this.borderColor, + this.lineHeight, }); factory FlowyTextButton.primary({ @@ -362,6 +363,7 @@ class FlowyTextButton extends StatelessWidget { final String? fontFamily; final bool isDangerous; final Color? borderColor; + final double? lineHeight; @override Widget build(BuildContext context) { @@ -375,6 +377,7 @@ class FlowyTextButton extends StatelessWidget { overflow: overflow, color: textColor, textAlign: TextAlign.center, + lineHeight: lineHeight, )); Widget child = Row( @@ -411,7 +414,7 @@ class FlowyTextButton extends StatelessWidget { fontSize: fontSize, decoration: decoration, fontFamily: fontFamily, - height: 1.1, + height: lineHeight ?? 1.1, ), ), backgroundColor: WidgetStateProperty.resolveWith( diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart index 687715c826..4593ee1d22 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart @@ -15,6 +15,8 @@ class PrimaryRoundedButton extends StatelessWidget { this.hoverColor, this.backgroundColor, this.useIntrinsicWidth = true, + this.lineHeight, + this.figmaLineHeight, }); final String text; @@ -27,6 +29,8 @@ class PrimaryRoundedButton extends StatelessWidget { final Color? hoverColor; final Color? backgroundColor; final bool useIntrinsicWidth; + final double? lineHeight; + final double? figmaLineHeight; @override Widget build(BuildContext context) { @@ -36,6 +40,8 @@ class PrimaryRoundedButton extends StatelessWidget { text, fontSize: fontSize ?? 14.0, fontWeight: fontWeight ?? FontWeight.w500, + lineHeight: lineHeight ?? 1.0, + figmaLineHeight: figmaLineHeight, color: Theme.of(context).colorScheme.onPrimary, textAlign: TextAlign.center, ), diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 58b2d7cc24..7196a64640 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1766,7 +1766,8 @@ "tooltip": "Click to open page" }, "deleted": "Deleted", - "deletedContent": "This content does not exist or has been deleted" + "deletedContent": "This content does not exist or has been deleted", + "noAccess": "No Access" }, "toolbar": { "resetToDefaultFont": "Reset to default" From 17c9c9b556fd22bd42cdab29b390753e5de186bd Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 13 Aug 2024 13:52:02 +0800 Subject: [PATCH 24/28] chore: optimize recent section performance (#5948) * fix: duplicated call for setting recent view * chore: reduce recent update --- .../workspace/application/tabs/tabs_bloc.dart | 7 +------ .../view_title/view_title_bar_bloc.dart | 16 ++++++++-------- .../presentation/widgets/view_title_bar.dart | 7 +++++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart index 36d64e6989..07f325f6c7 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart @@ -1,14 +1,12 @@ -import 'package:flutter/foundation.dart'; - import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/recent/cached_recent_service.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'tabs_bloc.freezed.dart'; @@ -92,8 +90,5 @@ class TabsBloc extends Bloc { view: view, ), ); - - // Update recent views - getIt().updateRecentViews([view.id], true); } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/view_title/view_title_bar_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view_title/view_title_bar_bloc.dart index 35f13d3de2..45f3eebaca 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view_title/view_title_bar_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view_title/view_title_bar_bloc.dart @@ -10,19 +10,19 @@ class ViewTitleBarBloc extends Bloc { ViewTitleBarBloc({ required this.view, }) : super(ViewTitleBarState.initial()) { + viewListener = ViewListener( + viewId: view.id, + )..start( + onViewChildViewsUpdated: (p0) { + add(const ViewTitleBarEvent.reload()); + }, + ); + on( (event, emit) async { await event.when( initial: () async { add(const ViewTitleBarEvent.reload()); - - viewListener = ViewListener( - viewId: view.id, - )..start( - onViewUpdated: (p0) { - add(const ViewTitleBarEvent.reload()); - }, - ); }, reload: () async { final List ancestors = diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart index 7be2a4b155..43b14d9d1a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart @@ -143,6 +143,13 @@ class _ViewTitleState extends State<_ViewTitle> { create: (_) => ViewTitleBloc(view: widget.view)..add(const ViewTitleEvent.initial()), child: BlocConsumer( + listenWhen: (previous, current) { + if (previous.view == null || current.view == null) { + return false; + } + + return previous.view != current.view; + }, listener: (_, state) { _resetTextEditingController(state); widget.onUpdated(); From f1ad03eaa94cb225713c49fe02289b547ee2301d Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 13 Aug 2024 15:03:57 +0800 Subject: [PATCH 25/28] chore: bump version 0.6.7 (#5949) * chore: update collab version * chore: update changelog * chore: update notification reddot style * fix: unable to fetch reminders * chore: optimize notification red dot size --- CHANGELOG.md | 11 ++++ .../mobile_bottom_navigation_bar.dart | 28 ++------ .../appflowy_flutter/lib/shared/red_dot.dart | 25 +++++++ .../widgets/notification_button.dart | 66 +++++++++---------- frontend/appflowy_tauri/src-tauri/Cargo.lock | 14 ++-- frontend/appflowy_tauri/src-tauri/Cargo.toml | 14 ++-- .../appflowy_web_app/src-tauri/Cargo.lock | 14 ++-- .../appflowy_web_app/src-tauri/Cargo.toml | 14 ++-- frontend/rust-lib/Cargo.lock | 14 ++-- frontend/rust-lib/Cargo.toml | 14 ++-- .../user_manager/manager_user_awareness.rs | 19 ++++-- 11 files changed, 129 insertions(+), 104 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/shared/red_dot.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f94103605..8bab3da249 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ # Release Notes +## Version 0.6.7 - 13/08/2024 +### New Features +- Redesigned the icon picker design on Desktop. +- Redesigned the notification page on Mobile. + +### Bug Fixes +- Enhance the toolbar tooltip functionality on Desktop. +- Enhance the slash menu user experience on Desktop. +- Fixed the issue where list style overrides occurred during text pasting. +- Fixed the issue where linking multiple databases in the same document could cause random loss of focus. + ## Version 0.6.6 - 30/07/2024 ### New Features - Upgrade your workspace to a premium plan to unlock more features and storage. diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index 5253022d40..1e1cd88ad9 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -4,6 +4,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/notifications/mobile_notifications_screen.dart'; import 'package:appflowy/mobile/presentation/widgets/navigation_bar_button.dart'; +import 'package:appflowy/shared/red_dot.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/util/theme_extension.dart'; @@ -162,7 +163,7 @@ class _NotificationNavigationBarItemIcon extends StatelessWidget { const Positioned( top: 2, right: 4, - child: _RedDot(), + child: NotificationRedDot(), ), ], ); @@ -172,25 +173,6 @@ class _NotificationNavigationBarItemIcon extends StatelessWidget { } } -class _RedDot extends StatelessWidget { - const _RedDot(); - - @override - Widget build(BuildContext context) { - return Container( - width: 6, - height: 6, - clipBehavior: Clip.antiAlias, - decoration: ShapeDecoration( - color: const Color(0xFFFF2214), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - ), - ); - } -} - class _HomePageNavigationBar extends StatelessWidget { const _HomePageNavigationBar({ required this.navigationShell, @@ -230,11 +212,13 @@ class _HomePageNavigationBar extends StatelessWidget { /// Navigate to the current location of the branch at the provided index when /// tapping an item in the BottomNavigationBar. void _onTap(BuildContext context, int bottomBarIndex) { - if (_items[bottomBarIndex].label == _addLabel) { + final label = _items[bottomBarIndex].label; + if (label == _addLabel) { // show an add dialog mobileCreateNewPageNotifier.value = ViewLayoutPB.Document; - return; + } else if (label == _notificationLabel) { + getIt().add(const ReminderEvent.refresh()); } // When navigating to a new branch, it's recommended to use the goBranch // method, as doing so makes sure the last navigation state of the diff --git a/frontend/appflowy_flutter/lib/shared/red_dot.dart b/frontend/appflowy_flutter/lib/shared/red_dot.dart new file mode 100644 index 0000000000..149cadae04 --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/red_dot.dart @@ -0,0 +1,25 @@ +import 'package:flutter/widgets.dart'; + +class NotificationRedDot extends StatelessWidget { + const NotificationRedDot({ + super.key, + this.size = 6, + }); + + final double size; + + @override + Widget build(BuildContext context) { + return Container( + width: size, + height: size, + clipBehavior: Clip.antiAlias, + decoration: ShapeDecoration( + color: const Color(0xFFFF2214), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/notifications/widgets/notification_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/notifications/widgets/notification_button.dart index 433511929f..e0747fc684 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/notifications/widgets/notification_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/notifications/widgets/notification_button.dart @@ -1,5 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/red_dot.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; @@ -7,7 +8,6 @@ import 'package:appflowy/workspace/application/settings/notifications/notificati import 'package:appflowy/workspace/presentation/notifications/notification_dialog.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -43,33 +43,35 @@ class _NotificationButtonState extends State { child: BlocBuilder( builder: (notificationSettingsContext, notificationSettingsState) { return BlocBuilder( - builder: (context, state) => notificationSettingsState - .isShowNotificationsIconEnabled - ? FlowyTooltip( - message: LocaleKeys.notificationHub_title.tr(), - child: AppFlowyPopover( - mutex: mutex, - direction: PopoverDirection.bottomWithLeftAligned, - constraints: - const BoxConstraints(maxHeight: 500, maxWidth: 425), - windowPadding: EdgeInsets.zero, - margin: EdgeInsets.zero, - popupBuilder: (_) => - NotificationDialog(views: views, mutex: mutex), - child: SizedBox.square( - dimension: 24.0, - child: FlowyButton( - useIntrinsicWidth: true, - margin: EdgeInsets.zero, - text: _buildNotificationIcon( - context, - state.hasUnreads, + builder: (context, state) { + final hasUnreads = state.reminders.any((r) => !r.isRead); + return notificationSettingsState.isShowNotificationsIconEnabled + ? FlowyTooltip( + message: LocaleKeys.notificationHub_title.tr(), + child: AppFlowyPopover( + mutex: mutex, + direction: PopoverDirection.bottomWithLeftAligned, + constraints: + const BoxConstraints(maxHeight: 500, maxWidth: 425), + windowPadding: EdgeInsets.zero, + margin: EdgeInsets.zero, + popupBuilder: (_) => + NotificationDialog(views: views, mutex: mutex), + child: SizedBox.square( + dimension: 24.0, + child: FlowyButton( + useIntrinsicWidth: true, + margin: EdgeInsets.zero, + text: _buildNotificationIcon( + context, + hasUnreads, + ), ), ), ), - ), - ) - : const SizedBox.shrink(), + ) + : const SizedBox.shrink(); + }, ); }, ), @@ -86,15 +88,11 @@ class _NotificationButtonState extends State { ), ), if (hasUnreads) - Positioned( - bottom: 2, - right: 2, - child: DecoratedBox( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AFThemeExtension.of(context).warning, - ), - child: const SizedBox(height: 8, width: 8), + const Positioned( + top: 4, + right: 6, + child: NotificationRedDot( + size: 5, ), ), ], diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 5232879284..64cc436a15 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -962,7 +962,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -986,7 +986,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -1016,7 +1016,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", @@ -1036,7 +1036,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "bytes", @@ -1055,7 +1055,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "chrono", @@ -1093,7 +1093,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-stream", @@ -1174,7 +1174,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index e1ea6f63ba..25aab8120f 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"] # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } # Working directory: frontend # To update the commit ID, run: diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock index d09880059e..7af7287706 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.lock +++ b/frontend/appflowy_web_app/src-tauri/Cargo.lock @@ -945,7 +945,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -969,7 +969,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -999,7 +999,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", @@ -1019,7 +1019,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "bytes", @@ -1038,7 +1038,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "chrono", @@ -1076,7 +1076,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-stream", @@ -1157,7 +1157,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml index b4db66e726..5d5dc9ec3a 100644 --- a/frontend/appflowy_web_app/src-tauri/Cargo.toml +++ b/frontend/appflowy_web_app/src-tauri/Cargo.toml @@ -116,13 +116,13 @@ custom-protocol = ["tauri/custom-protocol"] # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } # Working directory: frontend # To update the commit ID, run: diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 0785d7c68a..3b5e6896f4 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -823,7 +823,7 @@ dependencies = [ [[package]] name = "collab" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -847,7 +847,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-trait", @@ -877,7 +877,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", @@ -897,7 +897,7 @@ dependencies = [ [[package]] name = "collab-entity" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "bytes", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "chrono", @@ -954,7 +954,7 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "async-stream", @@ -1035,7 +1035,7 @@ dependencies = [ [[package]] name = "collab-user" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=aeca47a#aeca47a7b56414dcde90c73ba427dc5909318fc8" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=6adf750#6adf750dcb7a3f74806b8ffe8c7865bc9d5f85db" dependencies = [ "anyhow", "collab", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 1675bf3d7b..3875726ff9 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -136,13 +136,13 @@ rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb", rev = "1710120 # To switch to the local path, run: # scripts/tool/update_collab_source.sh # ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "aeca47a" } +collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } +collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "6adf750" } # Working directory: frontend # To update the commit ID, run: diff --git a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs index 826d665b39..ec6dab5499 100644 --- a/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs +++ b/frontend/rust-lib/flowy-user/src/user_manager/manager_user_awareness.rs @@ -256,15 +256,22 @@ impl UserManager { where F: FnOnce(&UserAwareness) -> Output, { + // Check if initialization is needed and perform it if necessary + if self.user_awareness.lock().await.is_none() { + if let Ok(session) = self.get_session() { + self.initialize_user_awareness(&session).await; + } + } + let user_awareness = self.user_awareness.lock().await; match &*user_awareness { - None => { - if let Ok(session) = self.get_session() { - self.initialize_user_awareness(&session).await; - } - default_value + Some(inner_awareness) => { + let inner_awareness_clone = inner_awareness.clone(); + drop(user_awareness); + let result = f(&inner_awareness_clone.lock()); + result }, - Some(user_awareness) => f(&user_awareness.lock()), + None => default_value, } } } From d23977ebb00ff72aa367de5aa80ed4d5b9b78108 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 13 Aug 2024 16:13:33 +0800 Subject: [PATCH 26/28] chore: optimize generation speed (#5930) * chore: optimize language generation speed * chore: optimize code generation speed * chore: optimize freezed generation speed * chore: optimzie appflowy_backend generation speed * chore: optimize appflowy_result * chore: optimzie flowy_infra generation speed * chore: optimzie flowy_infra_ui generation speed * chore: optimzie appflowy_flutter generation speed * chore: optimize generate.sh * chore: optimize the execution order * chore: use exclude_packages instead of include_packages --- frontend/appflowy_flutter/build.yaml | 0 .../packages/appflowy_backend/pubspec.yaml | 8 +- .../packages/appflowy_result/pubspec.yaml | 8 +- .../packages/flowy_infra/pubspec.yaml | 4 - .../packages/flowy_infra_ui/pubspec.yaml | 6 +- .../packages/flowy_svg/analysis_options.yaml | 2 - frontend/appflowy_flutter/pubspec.lock | 59 +--------- frontend/appflowy_flutter/pubspec.yaml | 14 +-- .../flowy_icons/generate_flowy_icons.sh | 67 ++++++++++- .../freezed/generate_freezed.sh | 107 ++++++++++++++---- frontend/scripts/code_generation/generate.sh | 65 ++++++++--- .../language_files/generate_language_files.sh | 72 ++++++++++-- frontend/scripts/makefile/flutter.toml | 2 +- 13 files changed, 275 insertions(+), 139 deletions(-) create mode 100644 frontend/appflowy_flutter/build.yaml diff --git a/frontend/appflowy_flutter/build.yaml b/frontend/appflowy_flutter/build.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml index 51ad23fde4..9ff267929a 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml @@ -14,20 +14,16 @@ dependencies: ffi: ^2.0.2 isolates: ^3.0.3+8 protobuf: ^3.1.0 - freezed_annotation: logger: ^2.4.0 plugin_platform_interface: ^2.1.3 - json_annotation: ^4.7.0 appflowy_result: path: ../appflowy_result + fixnum: ^1.1.0 + async: ^2.11.0 dev_dependencies: flutter_test: sdk: flutter - build_runner: - freezed: - flutter_lints: ^3.0.1 - json_serializable: ^6.6.2 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml index 241f437d9b..fa2e35f329 100644 --- a/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml @@ -4,16 +4,10 @@ version: 0.0.1 homepage: environment: - sdk: '>=3.3.0 <4.0.0' + sdk: ">=3.3.0 <4.0.0" flutter: ">=1.17.0" -dependencies: - flutter: - sdk: flutter - dev_dependencies: - flutter_test: - sdk: flutter flutter_lints: ^3.0.0 # For information on the generic Dart part of this file, see the diff --git a/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml index 0653aacaa5..ffe46b0aba 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml @@ -10,11 +10,9 @@ environment: dependencies: flutter: sdk: flutter - flutter_svg: ^2.0.2 json_annotation: ^4.7.0 path_provider: ^2.0.15 path: ^1.8.2 - textstyle_extensions: "2.0.0-nullsafety" time: ">=2.0.0" uuid: ">=2.2.2" bloc: ^8.1.2 @@ -23,8 +21,6 @@ dependencies: file: ^7.0.0 dev_dependencies: - flutter_test: - sdk: flutter build_runner: ^2.2.0 flutter_lints: ^3.0.1 freezed: ^2.4.7 diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml index 5eb1ba066e..c90679b0cd 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml @@ -13,9 +13,8 @@ dependencies: sdk: flutter # Thirdparty packages - provider: ^6.0.5 + styled_widget: ^0.4.1 - equatable: ^2.0.5 animations: ^2.0.7 loading_indicator: ^3.1.0 async: @@ -25,8 +24,6 @@ dependencies: # Federated Platform Interface flowy_infra_ui_platform_interface: path: flowy_infra_ui_platform_interface - flowy_infra_ui_web: - path: flowy_infra_ui_web appflowy_popover: path: ../appflowy_popover flowy_infra: @@ -35,6 +32,7 @@ dependencies: path: ../flowy_svg dev_dependencies: + provider: ^6.0.5 flutter_test: sdk: flutter flutter_lints: ^3.0.1 diff --git a/frontend/appflowy_flutter/packages/flowy_svg/analysis_options.yaml b/frontend/appflowy_flutter/packages/flowy_svg/analysis_options.yaml index 315807278e..543c78a7d4 100644 --- a/frontend/appflowy_flutter/packages/flowy_svg/analysis_options.yaml +++ b/frontend/appflowy_flutter/packages/flowy_svg/analysis_options.yaml @@ -1,3 +1 @@ -include: package:very_good_analysis/analysis_options.yaml - linter: diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 9b6b2102ef..183b341472 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -10,7 +10,7 @@ packages: source: hosted version: "67.0.0" analyzer: - dependency: "direct dev" + dependency: transitive description: name: analyzer sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" @@ -307,7 +307,7 @@ packages: source: hosted version: "1.3.0" charcode: - dependency: "direct main" + dependency: transitive description: name: charcode sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 @@ -322,14 +322,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" - clipboard: - dependency: "direct main" - description: - name: clipboard - sha256: "2ec38f0e59878008ceca0ab122e4bfde98847f88ef0f83331362ba4521f565a9" - url: "https://pub.dev" - source: hosted - version: "0.1.3" clock: dependency: transitive description: @@ -410,14 +402,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - custom_sliding_segmented_control: - dependency: "direct main" - description: - name: custom_sliding_segmented_control - sha256: "53c3e931c3ae1f696085d1ec70ac8e934da836595a9b7d9b88fdd0fcbf2a5574" - url: "https://pub.dev" - source: hosted - version: "1.8.3" dart_style: dependency: transitive description: @@ -663,13 +647,6 @@ packages: relative: true source: path version: "0.0.1" - flowy_infra_ui_web: - dependency: transitive - description: - path: "packages/flowy_infra_ui/flowy_infra_ui_web" - relative: true - source: path - version: "0.0.1" flowy_svg: dependency: "direct main" description: @@ -723,14 +700,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.13" - flutter_colorpicker: - dependency: "direct main" - description: - name: flutter_colorpicker - sha256: "458a6ed8ea480eb16ff892aedb4b7092b2804affd7e046591fb03127e8d8ef8b" - url: "https://pub.dev" - source: hosted - version: "1.0.3" flutter_driver: dependency: transitive description: flutter @@ -746,7 +715,7 @@ packages: source: git version: "1.0.2" flutter_highlight: - dependency: "direct main" + dependency: transitive description: name: flutter_highlight sha256: "7b96333867aa07e122e245c033b8ad622e4e3a42a1a2372cbb098a2541d8782c" @@ -778,7 +747,7 @@ packages: source: hosted version: "3.0.1" flutter_localizations: - dependency: "direct main" + dependency: transitive description: flutter source: sdk version: "0.0.0" @@ -966,7 +935,7 @@ packages: source: hosted version: "0.7.0" hive: - dependency: "direct main" + dependency: transitive description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" @@ -1037,14 +1006,6 @@ packages: url: "https://pub.dev" source: hosted version: "4.2.0" - image_gallery_saver: - dependency: "direct main" - description: - name: image_gallery_saver - sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816" - url: "https://pub.dev" - source: hosted - version: "2.0.3" image_picker: dependency: "direct main" description: @@ -1347,7 +1308,7 @@ packages: source: hosted version: "5.4.4" mocktail: - dependency: "direct main" + dependency: "direct dev" description: name: mocktail sha256: c4b5007d91ca4f67256e720cb1b6d704e79a510183a12fa551021f652577dce6 @@ -2153,14 +2114,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" - textstyle_extensions: - dependency: transitive - description: - name: textstyle_extensions - sha256: b0538352844fb4d1d0eea82f7bc6b96e4dae03a3a071247e4dcc85ec627b2c6c - url: "https://pub.dev" - source: hosted - version: "2.0.0-nullsafety" time: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 896bb4c73d..185ee51a4f 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -30,8 +30,6 @@ environment: dependencies: flutter: sdk: flutter - flutter_localizations: - sdk: flutter appflowy_backend: path: packages/appflowy_backend flowy_infra_ui: @@ -65,12 +63,10 @@ dependencies: sized_context: ^1.0.0+4 styled_widget: ^0.4.1 expandable: ^5.0.1 - flutter_colorpicker: ^1.0.3 flex_color_picker: ^3.5.1 highlight: ^0.7.0 package_info_plus: ^6.0.0 url_launcher: ^6.1.11 - clipboard: ^0.1.3 connectivity_plus: ^5.0.2 easy_localization: ^3.0.2 device_info_plus: ^10.1.0 @@ -83,7 +79,6 @@ dependencies: fixnum: ^1.1.0 flutter_svg: ^2.0.7 protobuf: ^3.1.0 - charcode: ^1.3.1 collection: ^1.17.1 bloc: ^8.1.2 shared_preferences: ^2.2.2 @@ -95,14 +90,13 @@ dependencies: ref: "6fe0c98" http: ^1.0.0 path: ^1.8.3 - mocktail: ^1.0.1 + archive: ^3.4.10 nanoid: ^1.0.0 supabase_flutter: ^1.10.4 envied: ^0.5.2 dotted_border: ^2.0.0+3 url_protocol: - hive: ^2.2.3 hive_flutter: ^1.1.0 super_clipboard: ^0.8.4 go_router: ^13.1.0 @@ -121,7 +115,6 @@ dependencies: app_links: ^3.5.0 flutter_slidable: ^3.0.0 image_picker: ^1.0.4 - image_gallery_saver: ^2.0.3 cached_network_image: ^3.3.0 leak_tracker: ^10.0.0 keyboard_height_plugin: ^0.1.5 @@ -157,8 +150,6 @@ dependencies: # BitsDojo Window for Windows bitsdojo_window: ^0.1.6 - flutter_highlight: ^0.7.0 - custom_sliding_segmented_control: ^1.8.3 toastification: ^2.0.0 scroll_to_index: ^3.0.1 extended_text_field: ^15.0.0 @@ -166,7 +157,6 @@ dependencies: dev_dependencies: flutter_lints: ^3.0.1 - analyzer: ^6.3.0 flutter_test: sdk: flutter @@ -182,6 +172,8 @@ dev_dependencies: url_launcher_platform_interface: any run_with_network_images: ^0.0.1 + mocktail: ^1.0.1 + dependency_overrides: http: ^1.0.0 diff --git a/frontend/scripts/code_generation/flowy_icons/generate_flowy_icons.sh b/frontend/scripts/code_generation/flowy_icons/generate_flowy_icons.sh index f4da25fb58..bff26c5b4b 100755 --- a/frontend/scripts/code_generation/flowy_icons/generate_flowy_icons.sh +++ b/frontend/scripts/code_generation/flowy_icons/generate_flowy_icons.sh @@ -1,6 +1,40 @@ #!/usr/bin/env bash -echo "Generating flowy icon files" +# check the cost time +start_time=$(date +%s) + +# read the arguments to skip the pub get and package get +skip_pub_get=false +skip_pub_packages_get=false +verbose=false +include_packages=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-pub-get) + skip_pub_get=true + shift + ;; + --skip-pub-packages-get) + skip_pub_packages_get=true + shift + ;; + --verbose) + verbose=true + shift + ;; + --exclude-packages) + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +echo "📷 Start generating image/svg files" # Store the current working directory original_dir=$(pwd) @@ -14,13 +48,34 @@ rm -rf assets/flowy_icons/ mkdir -p assets/flowy_icons/ rsync -r ../resources/flowy_icons/ assets/flowy_icons/ -flutter pub get -flutter packages pub get +if [ "$skip_pub_get" = false ]; then + if [ "$verbose" = true ]; then + flutter pub get + else + flutter pub get >/dev/null 2>&1 + fi +fi -echo "Generating FlowySvg classes" -dart run flowy_svg +if [ "$include_packages" = true ]; then + if [ "$verbose" = true ]; then + flutter packages pub get + else + flutter packages pub get >/dev/null 2>&1 + fi +fi -echo "Done generating icon files." +if [ "$verbose" = true ]; then + dart run flowy_svg +else + dart run flowy_svg >/dev/null 2>&1 +fi # Return to the original directory cd "$original_dir" + +echo "📷 Done generating image/svg files." + +# echo the cost time +end_time=$(date +%s) +cost_time=$((end_time - start_time)) +echo "📷 Image/svg files generation cost $cost_time seconds." diff --git a/frontend/scripts/code_generation/freezed/generate_freezed.sh b/frontend/scripts/code_generation/freezed/generate_freezed.sh index 01692fe7ee..4b7ded87c8 100755 --- a/frontend/scripts/code_generation/freezed/generate_freezed.sh +++ b/frontend/scripts/code_generation/freezed/generate_freezed.sh @@ -1,5 +1,40 @@ #!/usr/bin/env bash +# check the cost time +start_time=$(date +%s) + +# read the arguments to skip the pub get and package get +skip_pub_get=false +skip_pub_packages_get=false +verbose=false +exclude_packages=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-pub-get) + skip_pub_get=true + shift + ;; + --skip-pub-packages-get) + skip_pub_packages_get=true + shift + ;; + --verbose) + verbose=true + shift + ;; + --exclude-packages) + exclude_packages=true + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + # Store the current working directory original_dir=$(pwd) @@ -8,34 +43,60 @@ cd "$(dirname "$0")" # Navigate to the project root cd ../../../appflowy_flutter +if [ "$exclude_packages" = false ]; then + # Navigate to the packages directory + cd packages + for d in */; do + # Navigate into the subdirectory + cd "$d" + + # Check if the pubspec.yaml file exists and contains the freezed dependency + if [ -f "pubspec.yaml" ] && grep -q "build_runner" pubspec.yaml; then + echo "🧊 Start generating freezed files ($d)." + if [ "$skip_pub_packages_get" = false ]; then + if [ "$verbose" = true ]; then + flutter packages pub get + else + flutter packages pub get >/dev/null 2>&1 + fi + fi + if [ "$verbose" = true ]; then + dart run build_runner build + else + dart run build_runner build >/dev/null 2>&1 + fi + echo "🧊 Done generating freezed files ($d)." + fi + + # Navigate back to the packages directory + cd .. + done +fi + # Navigate to the appflowy_flutter directory and generate files -echo "Generating files for appflowy_flutter" +cd .. +echo "🧊 Start generating freezed files (AppFlowy)." -flutter packages pub get >/dev/null 2>&1 - -dart run build_runner build -d -echo "Done generating files for appflowy_flutter" - -echo "Generating files for packages" -cd packages -for d in */; do - # Navigate into the subdirectory - cd "$d" - - # Check if the subdirectory contains a pubspec.yaml file - if [ -f "pubspec.yaml" ]; then - echo "Generating freezed files in $d..." - echo "Please wait while we clean the project and fetch the dependencies." - flutter packages pub get >/dev/null 2>&1 - dart run build_runner build -d - echo "Done running build command in $d" +if [ "$skip_pub_packages_get" = false ]; then + if [ "$verbose" = true ]; then + flutter packages pub get else - echo "No pubspec.yaml found in $d, it can\'t be a Dart project. Skipping." + flutter packages pub get >/dev/null 2>&1 fi +fi - # Navigate back to the packages directory - cd .. -done +if [ "$verbose" = true ]; then + dart run build_runner build -d +else + dart run build_runner build >/dev/null 2>&1 +fi # Return to the original directory cd "$original_dir" + +echo "🧊 Done generating freezed files." + +# echo the cost time +end_time=$(date +%s) +cost_time=$((end_time - start_time)) +echo "🧊 Freezed files generation cost $cost_time seconds." diff --git a/frontend/scripts/code_generation/generate.sh b/frontend/scripts/code_generation/generate.sh index cfe7a12b60..afbf981a4a 100755 --- a/frontend/scripts/code_generation/generate.sh +++ b/frontend/scripts/code_generation/generate.sh @@ -1,5 +1,40 @@ #!/usr/bin/env bash +args=("$@") + +# check the cost time +start_time=$(date +%s) + +# read the arguments to skip the pub get and package get +skip_pub_get=false +skip_pub_packages_get=false +verbose=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-pub-get) + skip_pub_get=true + shift + ;; + --skip-pub-packages-get) + skip_pub_packages_get=true + shift + ;; + --verbose) + verbose=true + shift + ;; + --exclude-packages) + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + # Store the current working directory original_dir=$(pwd) @@ -7,30 +42,34 @@ original_dir=$(pwd) cd "$(dirname "$0")" # Call the script in the 'language_files' folder -echo "Generating files using easy_localization" cd language_files # Allow execution permissions on CI chmod +x ./generate_language_files.sh -./generate_language_files.sh "$@" +# Pass the arguments to the script +./generate_language_files.sh "${args[@]}" + +# Return to the main script directory +cd .. + +# Call the script in the 'flowy_icons' folder +cd flowy_icons +# Allow execution permissions on CI +chmod +x ./generate_flowy_icons.sh +./generate_flowy_icons.sh "${args[@]}" # Return to the main script directory cd .. # Call the script in the 'freezed' folder -echo "Generating files using build_runner" cd freezed # Allow execution permissions on CI chmod +x ./generate_freezed.sh -./generate_freezed.sh "$@" - -# Return to the main script directory -cd .. - -echo "Generating svg files using flowy_svg" -cd flowy_icons -# Allow execution permissions on CI -chmod +x ./generate_flowy_icons.sh -./generate_flowy_icons.sh "$@" +./generate_freezed.sh "${args[@]}" # Return to the original directory cd "$original_dir" + +# echo the cost time +end_time=$(date +%s) +cost_time=$((end_time - start_time)) +echo "✅ Code generation cost $cost_time seconds." diff --git a/frontend/scripts/code_generation/language_files/generate_language_files.sh b/frontend/scripts/code_generation/language_files/generate_language_files.sh index ec5a14836e..5e51b5cdba 100755 --- a/frontend/scripts/code_generation/language_files/generate_language_files.sh +++ b/frontend/scripts/code_generation/language_files/generate_language_files.sh @@ -1,6 +1,41 @@ #!/usr/bin/env bash -echo "Generating language files" +set -e + +# check the cost time +start_time=$(date +%s) + +# read the arguments to skip the pub get and package get +skip_pub_get=false +skip_pub_packages_get=false +verbose=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case "$1" in + --skip-pub-get) + skip_pub_get=true + shift + ;; + --skip-pub-packages-get) + skip_pub_packages_get=true + shift + ;; + --verbose) + verbose=true + shift + ;; + --exclude-packages) + shift + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done + +echo "🌍 Start generating language files." # Store the current working directory original_dir=$(pwd) @@ -19,16 +54,35 @@ cp -f ../resources/translations/*.json assets/translations/ # the ci alwayas return a 'null check operator used on a null value' error. # so we force to exec the below command to avoid the error. # https://github.com/dart-lang/pub/issues/3314 -flutter pub get -flutter packages pub get +if [ "$skip_pub_get" = false ]; then + if [ "$verbose" = true ]; then + flutter pub get + else + flutter pub get >/dev/null 2>&1 + fi +fi +if [ "$skip_pub_packages_get" = false ]; then + if [ "$verbose" = true ]; then + flutter packages pub get + else + flutter packages pub get >/dev/null 2>&1 + fi +fi -echo "Specifying source directory for AppFlowy Localizations." -dart run easy_localization:generate -S assets/translations/ +if [ "$verbose" = true ]; then + dart run easy_localization:generate -S assets/translations/ + dart run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations/ -s en.json +else + dart run easy_localization:generate -S assets/translations/ >/dev/null 2>&1 + dart run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations/ -s en.json >/dev/null 2>&1 +fi -echo "Generating language files for AppFlowy." -dart run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations/ -s en.json - -echo "Done generating language files." +echo "🌍 Done generating language files." # Return to the original directory cd "$original_dir" + +# echo the cost time +end_time=$(date +%s) +cost_time=$((end_time - start_time)) +echo "🌍 Language files generation cost $cost_time seconds." diff --git a/frontend/scripts/makefile/flutter.toml b/frontend/scripts/makefile/flutter.toml index 4203ce678d..e62acac3f6 100644 --- a/frontend/scripts/makefile/flutter.toml +++ b/frontend/scripts/makefile/flutter.toml @@ -301,7 +301,7 @@ script = [""" [tasks.code_generation] script_runner = "@shell" script = [""" - sh scripts/code_generation/generate.sh + ./scripts/code_generation/generate.sh """] [tasks.code_generation.windows] From e2359cf047f30b23c47c621cd7d0232a334c37c7 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Tue, 13 Aug 2024 20:01:32 +0800 Subject: [PATCH 27/28] fix: row property align issue (#5950) * fix: row property align issue * fix: generate_freezed.sh path warning * Revert "fix: generate_freezed.sh path warning" This reverts commit 7c0a4a3177702d5b58858296133876a04ebe274d. * fix: generate_freezed.sh path warning * chore: improve chat page mobile UI --- frontend/appflowy_flutter/ios/Podfile.lock | 6 ----- .../widgets/mobile_row_property_list.dart | 1 + .../presentation/chat_input/chat_input.dart | 12 +++++---- .../lib/startup/tasks/generate_router.dart | 27 ++++++++++++++++--- frontend/appflowy_flutter/pubspec.lock | 4 +-- frontend/appflowy_flutter/pubspec.yaml | 2 +- .../freezed/generate_freezed.sh | 3 ++- 7 files changed, 37 insertions(+), 18 deletions(-) diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock index c54ae23ed6..d7647a9d4a 100644 --- a/frontend/appflowy_flutter/ios/Podfile.lock +++ b/frontend/appflowy_flutter/ios/Podfile.lock @@ -48,8 +48,6 @@ PODS: - fluttertoast (0.0.2): - Flutter - Toast - - image_gallery_saver (2.0.2): - - Flutter - image_picker_ios (0.0.1): - Flutter - integration_test (0.0.1): @@ -95,7 +93,6 @@ DEPENDENCIES: - flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`) - Flutter (from `Flutter`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`) @@ -136,8 +133,6 @@ EXTERNAL SOURCES: :path: Flutter fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" - image_gallery_saver: - :path: ".symlinks/plugins/image_gallery_saver/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" integration_test: @@ -176,7 +171,6 @@ SPEC CHECKSUMS: flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425 integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9 diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart index 0498427547..7c26879a4f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/mobile_row_property_list.dart @@ -87,6 +87,7 @@ class _PropertyCellState extends State<_PropertyCell> { fieldInfo.name, overflow: TextOverflow.ellipsis, fontSize: 14, + figmaLineHeight: 16.0, color: Theme.of(context).hintColor, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart index 60de3fd528..54c70d61cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input.dart @@ -12,6 +12,7 @@ import 'package:extended_text_field/extended_text_field.dart'; import 'package:flowy_infra/file_picker/file_picker_service.dart'; import 'package:flowy_infra/platform_extension.dart'; import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -21,8 +22,8 @@ import 'package:flutter_chat_ui/flutter_chat_ui.dart'; import 'chat_at_button.dart'; import 'chat_input_attachment.dart'; -import 'chat_send_button.dart'; import 'chat_input_span.dart'; +import 'chat_send_button.dart'; import 'layout_define.dart'; class ChatInput extends StatefulWidget { @@ -114,7 +115,7 @@ class _ChatInputState extends State { child: Container( decoration: BoxDecoration( border: Border.all( - color: _inputFocusNode.hasFocus && !isMobile + color: _inputFocusNode.hasFocus ? Theme.of(context).colorScheme.primary.withOpacity(0.6) : Theme.of(context).colorScheme.secondary, ), @@ -161,9 +162,9 @@ class _ChatInputState extends State { Expanded(child: _inputTextField(context, textPadding)), // mention button - // TODO(lucas): support mobile - if (PlatformExtension.isDesktop) - _mentionButton(buttonPadding), + _mentionButton(buttonPadding), + + if (PlatformExtension.isMobile) const HSpace(6.0), // send button _sendButton(buttonPadding), @@ -245,6 +246,7 @@ class _ChatInputState extends State { InputDecoration _buildInputDecoration(BuildContext context) { return InputDecoration( border: InputBorder.none, + enabledBorder: InputBorder.none, hintText: widget.hintText, focusedBorder: InputBorder.none, hintStyle: TextStyle( diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index 127f365bf6..7e10166fe4 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -31,6 +31,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/m import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra/time/duration.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:sheet/route.dart'; @@ -558,10 +559,25 @@ GoRoute _mobileCardDetailScreenRoute() { parentNavigatorKey: AppGlobals.rootNavKey, path: MobileRowDetailPage.routeName, pageBuilder: (context, state) { - final args = state.extra as Map; + var extra = state.extra as Map?; + + if (kDebugMode && extra == null) { + extra = _dynamicValues; + } + + if (extra == null) { + return const MaterialExtendedPage( + child: SizedBox.shrink(), + ); + } + final databaseController = - args[MobileRowDetailPage.argDatabaseController]; - final rowId = args[MobileRowDetailPage.argRowId]!; + extra[MobileRowDetailPage.argDatabaseController]; + final rowId = extra[MobileRowDetailPage.argRowId]!; + + if (kDebugMode) { + _dynamicValues = extra; + } return MaterialExtendedPage( child: MobileRowDetailPage( @@ -629,3 +645,8 @@ Widget _buildFadeTransition( Duration _slowDuration = Duration( milliseconds: RouteDurations.slow.inMilliseconds.round(), ); + +// ONLY USE IN DEBUG MODE +// this is a workaround for the issue of GoRouter not supporting extra with complex types +// https://github.com/flutter/flutter/issues/137248 +Map _dynamicValues = {}; diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 183b341472..5d16830e41 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -890,10 +890,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "170c46e237d6eb0e6e9f0e8b3f56101e14fb64f787016e42edd74c39cf8b176a" + sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe url: "https://pub.dev" source: hosted - version: "13.2.0" + version: "14.2.3" google_fonts: dependency: "direct main" description: diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 185ee51a4f..b8caf2c183 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -99,7 +99,7 @@ dependencies: url_protocol: hive_flutter: ^1.1.0 super_clipboard: ^0.8.4 - go_router: ^13.1.0 + go_router: ^14.2.0 string_validator: ^1.0.0 unsplash_client: ^2.1.1 flutter_emoji_mart: diff --git a/frontend/scripts/code_generation/freezed/generate_freezed.sh b/frontend/scripts/code_generation/freezed/generate_freezed.sh index 4b7ded87c8..391aea08b2 100755 --- a/frontend/scripts/code_generation/freezed/generate_freezed.sh +++ b/frontend/scripts/code_generation/freezed/generate_freezed.sh @@ -71,10 +71,11 @@ if [ "$exclude_packages" = false ]; then # Navigate back to the packages directory cd .. done + + cd .. fi # Navigate to the appflowy_flutter directory and generate files -cd .. echo "🧊 Start generating freezed files (AppFlowy)." if [ "$skip_pub_packages_get" = false ]; then From 463c8c7ee4d75658a46b55bd6dfd87ec0c2bcb97 Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Tue, 13 Aug 2024 23:36:44 +0800 Subject: [PATCH 28/28] feat: enable local set (#5955) * chore: enable local set * chore: fix test * chore: clippy * chore: fix tauri build * chore: fix tauri build --- frontend/appflowy_tauri/src-tauri/src/init.rs | 15 +- .../appflowy_tauri/src-tauri/src/request.rs | 8 +- .../appflowy_web_app/src-tauri/src/init.rs | 40 +++-- .../appflowy_web_app/src-tauri/src/request.rs | 8 +- frontend/rust-lib/dart-ffi/Cargo.toml | 2 +- frontend/rust-lib/dart-ffi/src/lib.rs | 9 +- .../src/event_builder.rs | 13 +- .../event-integration-test/src/lib.rs | 11 +- .../af_cloud_test/file_upload_test.rs | 28 ++-- .../folder/local_test/subscription_test.rs | 63 +++----- .../user/af_cloud_test/workspace_test.rs | 22 ++- .../rust-lib/flowy-ai/src/event_handler.rs | 28 ++-- .../src/local_ai/local_llm_resource.rs | 2 +- frontend/rust-lib/flowy-core/src/lib.rs | 22 ++- frontend/rust-lib/lib-dispatch/Cargo.toml | 2 +- .../rust-lib/lib-dispatch/src/dispatcher.rs | 146 +++++++++--------- .../lib-dispatch/src/module/module.rs | 24 +-- frontend/rust-lib/lib-dispatch/src/runtime.rs | 58 +++---- .../lib-dispatch/src/service/boxed.rs | 8 +- .../rust-lib/lib-dispatch/tests/api/module.rs | 6 +- 20 files changed, 250 insertions(+), 265 deletions(-) diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs index 7591ba37ff..636735e5f4 100644 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ b/frontend/appflowy_tauri/src-tauri/src/init.rs @@ -1,7 +1,7 @@ use flowy_core::config::AppFlowyCoreConfig; -use flowy_core::{AppFlowyCore, DEFAULT_NAME}; +use flowy_core::{AppFlowyCore, MutexAppFlowyCore, DEFAULT_NAME}; use lib_dispatch::runtime::AFPluginRuntime; -use std::sync::Arc; +use std::rc::Rc; use dotenv::dotenv; @@ -25,7 +25,7 @@ pub fn read_env() { } } -pub fn init_flowy_core() -> AppFlowyCore { +pub fn init_flowy_core() -> MutexAppFlowyCore { let config_json = include_str!("../tauri.conf.json"); let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap(); @@ -35,7 +35,8 @@ pub fn init_flowy_core() -> AppFlowyCore { .clone() .map(|v| v.to_string()) .unwrap_or_else(|| "0.5.8".to_string()); - let app_version = semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); + let app_version = + semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); if cfg!(debug_assertions) { data_path.push("data_dev"); @@ -60,7 +61,9 @@ pub fn init_flowy_core() -> AppFlowyCore { ) .log_filter("trace", vec!["appflowy_tauri".to_string()]); - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); + let runtime = Rc::new(AFPluginRuntime::new().unwrap()); let cloned_runtime = runtime.clone(); - runtime.block_on(async move { AppFlowyCore::new(config, cloned_runtime, None).await }) + runtime.block_on(async move { + MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await) + }) } diff --git a/frontend/appflowy_tauri/src-tauri/src/request.rs b/frontend/appflowy_tauri/src-tauri/src/request.rs index 029e71c18c..6d2d01fb6e 100644 --- a/frontend/appflowy_tauri/src-tauri/src/request.rs +++ b/frontend/appflowy_tauri/src-tauri/src/request.rs @@ -1,4 +1,4 @@ -use flowy_core::AppFlowyCore; +use flowy_core::MutexAppFlowyCore; use lib_dispatch::prelude::{ AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, }; @@ -38,8 +38,8 @@ pub async fn invoke_request( app_handler: AppHandle, ) -> AFTauriResponse { let request: AFPluginRequest = request.into(); - let state: State = app_handler.state(); - let dispatcher = state.inner().dispatcher(); - let response = AFPluginDispatcher::async_send(dispatcher.as_ref(), request).await; + let state: State = app_handler.state(); + let dispatcher = state.0.lock().dispatcher(); + let response = AFPluginDispatcher::sync_send(dispatcher, request); response.into() } diff --git a/frontend/appflowy_web_app/src-tauri/src/init.rs b/frontend/appflowy_web_app/src-tauri/src/init.rs index 42c857abdf..636735e5f4 100644 --- a/frontend/appflowy_web_app/src-tauri/src/init.rs +++ b/frontend/appflowy_web_app/src-tauri/src/init.rs @@ -1,7 +1,7 @@ use flowy_core::config::AppFlowyCoreConfig; -use flowy_core::{AppFlowyCore, DEFAULT_NAME}; +use flowy_core::{AppFlowyCore, MutexAppFlowyCore, DEFAULT_NAME}; use lib_dispatch::runtime::AFPluginRuntime; -use std::sync::Arc; +use std::rc::Rc; use dotenv::dotenv; @@ -9,28 +9,34 @@ pub fn read_env() { dotenv().ok(); let env = if cfg!(debug_assertions) { - include_str!("../env.development") + include_str!("../env.development") } else { - include_str!("../env.production") + include_str!("../env.production") }; for line in env.lines() { - if let Some((key, value)) = line.split_once('=') { - // Check if the environment variable is not already set in the system - let current_value = std::env::var(key).unwrap_or_default(); - if current_value.is_empty() { - std::env::set_var(key, value); - } + if let Some((key, value)) = line.split_once('=') { + // Check if the environment variable is not already set in the system + let current_value = std::env::var(key).unwrap_or_default(); + if current_value.is_empty() { + std::env::set_var(key, value); } + } } } -pub fn init_flowy_core() -> AppFlowyCore { +pub fn init_flowy_core() -> MutexAppFlowyCore { let config_json = include_str!("../tauri.conf.json"); let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap(); - let app_version = config.package.version.clone().map(|v| v.to_string()).unwrap_or_else(|| "0.0.0".to_string()); - let app_version = semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); + let app_version = config + .package + .version + .clone() + .map(|v| v.to_string()) + .unwrap_or_else(|| "0.5.8".to_string()); + let app_version = + semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); if cfg!(debug_assertions) { data_path.push("data_dev"); @@ -50,12 +56,14 @@ pub fn init_flowy_core() -> AppFlowyCore { custom_application_path, application_path, device_id, - "web".to_string(), + "tauri".to_string(), DEFAULT_NAME.to_string(), ) .log_filter("trace", vec!["appflowy_tauri".to_string()]); - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); + let runtime = Rc::new(AFPluginRuntime::new().unwrap()); let cloned_runtime = runtime.clone(); - runtime.block_on(async move { AppFlowyCore::new(config, cloned_runtime, None).await }) + runtime.block_on(async move { + MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await) + }) } diff --git a/frontend/appflowy_web_app/src-tauri/src/request.rs b/frontend/appflowy_web_app/src-tauri/src/request.rs index 029e71c18c..0ec6a8dadc 100644 --- a/frontend/appflowy_web_app/src-tauri/src/request.rs +++ b/frontend/appflowy_web_app/src-tauri/src/request.rs @@ -1,4 +1,4 @@ -use flowy_core::AppFlowyCore; +use flowy_core::{AppFlowyCore, MutexAppFlowyCore}; use lib_dispatch::prelude::{ AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, }; @@ -38,8 +38,8 @@ pub async fn invoke_request( app_handler: AppHandle, ) -> AFTauriResponse { let request: AFPluginRequest = request.into(); - let state: State = app_handler.state(); - let dispatcher = state.inner().dispatcher(); - let response = AFPluginDispatcher::async_send(dispatcher.as_ref(), request).await; + let state: State = app_handler.state(); + let dispatcher = state.0.lock().dispatcher(); + let response = AFPluginDispatcher::sync_send(dispatcher, request); response.into() } diff --git a/frontend/rust-lib/dart-ffi/Cargo.toml b/frontend/rust-lib/dart-ffi/Cargo.toml index bca0489e7b..22e07f3483 100644 --- a/frontend/rust-lib/dart-ffi/Cargo.toml +++ b/frontend/rust-lib/dart-ffi/Cargo.toml @@ -28,7 +28,7 @@ lib-log.workspace = true semver = "1.0.22" # workspace -lib-dispatch = { workspace = true } +lib-dispatch = { workspace = true, features = ["local_set"] } # Core #flowy-core = { workspace = true, features = ["profiling"] } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index aecd9bef28..14b5a13a24 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -4,6 +4,7 @@ use allo_isolate::Isolate; use lazy_static::lazy_static; use parking_lot::Mutex; use semver::Version; +use std::rc::Rc; use std::sync::Arc; use std::{ffi::CStr, os::raw::c_char}; use tracing::{debug, error, info, trace, warn}; @@ -37,14 +38,14 @@ lazy_static! { static ref LOG_STREAM_ISOLATE: Mutex> = Mutex::new(None); } -struct MutexAppFlowyCore(Arc>>); +struct MutexAppFlowyCore(Rc>>); impl MutexAppFlowyCore { fn new() -> Self { - Self(Arc::new(Mutex::new(None))) + Self(Rc::new(Mutex::new(None))) } - fn dispatcher(&self) -> Option> { + fn dispatcher(&self) -> Option> { let binding = self.0.lock(); let core = binding.as_ref(); core.map(|core| core.event_dispatcher.clone()) @@ -90,7 +91,7 @@ pub extern "C" fn init_sdk(_port: i64, data: *mut c_char) -> i64 { core.close_db(); } - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); + let runtime = Rc::new(AFPluginRuntime::new().unwrap()); let cloned_runtime = runtime.clone(); let log_stream = LOG_STREAM_ISOLATE diff --git a/frontend/rust-lib/event-integration-test/src/event_builder.rs b/frontend/rust-lib/event-integration-test/src/event_builder.rs index 0d083b1037..5168723981 100644 --- a/frontend/rust-lib/event-integration-test/src/event_builder.rs +++ b/frontend/rust-lib/event-integration-test/src/event_builder.rs @@ -1,13 +1,12 @@ +use flowy_user::errors::{internal_error, FlowyError}; +use lib_dispatch::prelude::{ + AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, ToBytes, *, +}; +use std::rc::Rc; use std::{ convert::TryFrom, fmt::{Debug, Display}, hash::Hash, - sync::Arc, -}; - -use flowy_user::errors::{internal_error, FlowyError}; -use lib_dispatch::prelude::{ - AFPluginDispatcher, AFPluginEventResponse, AFPluginFromBytes, AFPluginRequest, ToBytes, *, }; use crate::EventIntegrationTest; @@ -86,7 +85,7 @@ impl EventBuilder { .map(|data| data.into_inner()) } - fn dispatch(&self) -> Arc { + fn dispatch(&self) -> Rc { self.context.sdk.dispatcher() } diff --git a/frontend/rust-lib/event-integration-test/src/lib.rs b/frontend/rust-lib/event-integration-test/src/lib.rs index cd2a01d84f..e368c4168c 100644 --- a/frontend/rust-lib/event-integration-test/src/lib.rs +++ b/frontend/rust-lib/event-integration-test/src/lib.rs @@ -5,6 +5,7 @@ use collab_document::document::Document; use collab_entity::CollabType; use std::env::temp_dir; use std::path::PathBuf; +use std::rc::Rc; use std::sync::Arc; use std::time::Duration; @@ -163,13 +164,9 @@ pub fn document_from_document_doc_state(doc_id: &str, doc_state: Vec) -> Doc } async fn init_core(config: AppFlowyCoreConfig) -> AppFlowyCore { - std::thread::spawn(|| { - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); - let cloned_runtime = runtime.clone(); - runtime.block_on(async move { AppFlowyCore::new(config, cloned_runtime, None).await }) - }) - .join() - .unwrap() + let runtime = Rc::new(AFPluginRuntime::new().unwrap()); + let cloned_runtime = runtime.clone(); + AppFlowyCore::new(config, cloned_runtime, None).await } impl std::ops::Deref for EventIntegrationTest { diff --git a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs index cf0050341e..a2ab2d1245 100644 --- a/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/document/af_cloud_test/file_upload_test.rs @@ -94,30 +94,24 @@ async fn af_cloud_upload_6_files_test() { // Wait for all uploads to finish let uploads = Arc::new(Mutex::new(created_uploads)); let mut handles = vec![]; + for mut receiver in receivers { let cloned_uploads = uploads.clone(); - let cloned_test = test.clone(); + let state = test.storage_manager.get_file_state(&receiver.file_id).await; let handle = tokio::spawn(async move { - if let Some(state) = cloned_test - .storage_manager - .get_file_state(&receiver.file_id) - .await - { - if let FileUploadState::Finished { file_id } = state { + if let Some(FileUploadState::Finished { file_id }) = state { + cloned_uploads + .lock() + .await + .retain(|upload| upload.file_id != file_id); + } + while let Some(value) = receiver.recv().await { + if let FileUploadState::Finished { file_id } = value { cloned_uploads .lock() .await .retain(|upload| upload.file_id != file_id); - } - } else { - while let Some(value) = receiver.recv().await { - if let FileUploadState::Finished { file_id } = value { - cloned_uploads - .lock() - .await - .retain(|upload| upload.file_id != file_id); - break; - } + break; } } }); diff --git a/frontend/rust-lib/event-integration-test/tests/folder/local_test/subscription_test.rs b/frontend/rust-lib/event-integration-test/tests/folder/local_test/subscription_test.rs index 089bbae7ba..c9460d9db0 100644 --- a/frontend/rust-lib/event-integration-test/tests/folder/local_test/subscription_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/folder/local_test/subscription_test.rs @@ -24,11 +24,9 @@ async fn create_child_view_in_workspace_subscription_test() { let cloned_test = test.clone(); let cloned_workspace_id = workspace.id.clone(); - test.appflowy_core.dispatcher().spawn(async move { - cloned_test - .create_view(&cloned_workspace_id, "workspace child view".to_string()) - .await; - }); + cloned_test + .create_view(&cloned_workspace_id, "workspace child view".to_string()) + .await; let views = receive_with_timeout(rx, Duration::from_secs(30)) .await @@ -50,14 +48,17 @@ async fn create_child_view_in_view_subscription_test() { let cloned_test = test.clone(); let child_view_id = workspace_child_view.id.clone(); - test.appflowy_core.dispatcher().spawn(async move { - cloned_test - .create_view( - &child_view_id, - "workspace child view's child view".to_string(), - ) - .await; - }); + let local_set = tokio::task::LocalSet::new(); + local_set + .run_until(async move { + cloned_test + .create_view( + &child_view_id, + "workspace child view's child view".to_string(), + ) + .await; + }) + .await; let update = receive_with_timeout(rx, Duration::from_secs(30)) .await @@ -81,22 +82,11 @@ async fn delete_view_subscription_test() { let cloned_test = test.clone(); let delete_view_id = workspace.views.first().unwrap().id.clone(); let cloned_delete_view_id = delete_view_id.clone(); - test - .appflowy_core - .dispatcher() - .spawn(async move { - cloned_test.delete_view(&cloned_delete_view_id).await; - }) + + cloned_test.delete_view(&cloned_delete_view_id).await; + let update = receive_with_timeout(rx, Duration::from_secs(60)) .await .unwrap(); - - let update = test - .appflowy_core - .dispatcher() - .run_until(receive_with_timeout(rx, Duration::from_secs(60))) - .await - .unwrap(); - assert_eq!(update.delete_child_views.len(), 1); assert_eq!(update.delete_child_views[0], delete_view_id); } @@ -114,17 +104,14 @@ async fn update_view_subscription_test() { assert!(!view.is_favorite); let update_view_id = view.id.clone(); - test.appflowy_core.dispatcher().spawn(async move { - cloned_test - .update_view(UpdateViewPayloadPB { - view_id: update_view_id, - name: Some("hello world".to_string()), - is_favorite: Some(true), - ..Default::default() - }) - .await; - }); - + cloned_test + .update_view(UpdateViewPayloadPB { + view_id: update_view_id, + name: Some("hello world".to_string()), + is_favorite: Some(true), + ..Default::default() + }) + .await; let update = receive_with_timeout(rx, Duration::from_secs(30)) .await .unwrap(); diff --git a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs index b72ceba33f..c35224ea99 100644 --- a/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/user/af_cloud_test/workspace_test.rs @@ -5,6 +5,7 @@ use collab_folder::Folder; use event_integration_test::user_event::user_localhost_af_cloud; use event_integration_test::EventIntegrationTest; use std::time::Duration; +use tokio::task::LocalSet; use tokio::time::sleep; use crate::user::af_cloud_test::util::get_synced_workspaces; @@ -158,9 +159,9 @@ async fn af_cloud_different_open_same_workspace_test() { user_localhost_af_cloud().await; // Set up the primary client and sign them up to the cloud. - let client_1 = EventIntegrationTest::new().await; - let owner_profile = client_1.af_cloud_sign_up().await; - let shared_workspace_id = client_1.get_current_workspace().await.id.clone(); + let test_runner = EventIntegrationTest::new().await; + let owner_profile = test_runner.af_cloud_sign_up().await; + let shared_workspace_id = test_runner.get_current_workspace().await.id.clone(); // Verify that the workspace ID from the profile matches the current session's workspace ID. assert_eq!(shared_workspace_id, owner_profile.workspace_id); @@ -181,7 +182,7 @@ async fn af_cloud_different_open_same_workspace_test() { client.delete_view(&view.id).await; } - client_1 + test_runner .add_workspace_member(&owner_profile.workspace_id, &client) .await; clients.push((client, client_profile)); @@ -195,9 +196,10 @@ async fn af_cloud_different_open_same_workspace_test() { // Simulate each client open different workspace 30 times let mut handles = vec![]; + let local_set = LocalSet::new(); for client in clients.clone() { let cloned_shared_workspace_id = shared_workspace_id.clone(); - let handle = tokio::spawn(async move { + let handle = local_set.spawn_local(async move { let (client, profile) = client; let all_workspaces = get_synced_workspaces(&client, profile.id).await; for i in 0..30 { @@ -216,10 +218,16 @@ async fn af_cloud_different_open_same_workspace_test() { }); handles.push(handle); } - futures::future::join_all(handles).await; + let results = local_set + .run_until(futures::future::join_all(handles)) + .await; + + for result in results { + assert!(result.is_ok()); + } // Retrieve and verify the collaborative document state for Client 1's workspace. - let doc_state = client_1 + let doc_state = test_runner .get_collab_doc_state(&shared_workspace_id, CollabType::Folder) .await .unwrap(); diff --git a/frontend/rust-lib/flowy-ai/src/event_handler.rs b/frontend/rust-lib/flowy-ai/src/event_handler.rs index 21f14070f4..3ec6eeb660 100644 --- a/frontend/rust-lib/flowy-ai/src/event_handler.rs +++ b/frontend/rust-lib/flowy-ai/src/event_handler.rs @@ -63,24 +63,18 @@ pub(crate) async fn stream_chat_message_handler( .collect::>(); trace!("Stream chat message with metadata: {:?}", metadata); - let (tx, rx) = oneshot::channel::>(); let ai_manager = upgrade_ai_manager(ai_manager)?; - tokio::spawn(async move { - let result = ai_manager - .stream_chat_message( - &data.chat_id, - &data.message, - message_type, - data.answer_stream_port, - data.question_stream_port, - metadata, - ) - .await; - let _ = tx.send(result); - }); - - let question = rx.await??; - data_result_ok(question) + let result = ai_manager + .stream_chat_message( + &data.chat_id, + &data.message, + message_type, + data.answer_stream_port, + data.question_stream_port, + metadata, + ) + .await?; + data_result_ok(result) } #[tracing::instrument(level = "debug", skip_all, err)] diff --git a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs index 5528a00ac5..457322a111 100644 --- a/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs +++ b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs @@ -288,7 +288,7 @@ impl LocalAIResourceController { while let Ok(value) = rx.recv().await { let is_finish = value == DOWNLOAD_FINISH; if let Err(err) = progress_sink.send(value).await { - error!("Failed to send progress: {:?}", err); + warn!("Failed to send progress: {:?}", err); break; } diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index 761708bfe5..ae5b1d801d 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -2,6 +2,8 @@ use flowy_search::folder::indexer::FolderIndexManagerImpl; use flowy_search::services::manager::SearchManager; +use parking_lot::Mutex; +use std::rc::Rc; use std::sync::{Arc, Weak}; use std::time::Duration; use sysinfo::System; @@ -54,7 +56,7 @@ pub struct AppFlowyCore { pub document_manager: Arc, pub folder_manager: Arc, pub database_manager: Arc, - pub event_dispatcher: Arc, + pub event_dispatcher: Rc, pub server_provider: Arc, pub task_dispatcher: Arc>, pub store_preference: Arc, @@ -66,7 +68,7 @@ pub struct AppFlowyCore { impl AppFlowyCore { pub async fn new( config: AppFlowyCoreConfig, - runtime: Arc, + runtime: Rc, stream_log_sender: Option>, ) -> Self { let platform = OperatingSystem::from(&config.platform); @@ -102,7 +104,7 @@ impl AppFlowyCore { } #[instrument(skip(config, runtime))] - async fn init(config: AppFlowyCoreConfig, runtime: Arc) -> Self { + async fn init(config: AppFlowyCoreConfig, runtime: Rc) -> Self { // Init the key value database let store_preference = Arc::new(KVStorePreferences::new(&config.storage_path).unwrap()); info!("🔥{:?}", &config); @@ -261,7 +263,7 @@ impl AppFlowyCore { error!("Init user failed: {}", err) } } - let event_dispatcher = Arc::new(AFPluginDispatcher::new( + let event_dispatcher = Rc::new(AFPluginDispatcher::new( runtime, make_plugins( Arc::downgrade(&folder_manager), @@ -290,7 +292,7 @@ impl AppFlowyCore { } /// Only expose the dispatcher in test - pub fn dispatcher(&self) -> Arc { + pub fn dispatcher(&self) -> Rc { self.event_dispatcher.clone() } } @@ -321,3 +323,13 @@ impl ServerUser for ServerUserImpl { self.upgrade_user()?.workspace_id() } } + +pub struct MutexAppFlowyCore(pub Rc>); + +impl MutexAppFlowyCore { + pub fn new(appflowy_core: AppFlowyCore) -> Self { + Self(Rc::new(Mutex::new(appflowy_core))) + } +} +unsafe impl Sync for MutexAppFlowyCore {} +unsafe impl Send for MutexAppFlowyCore {} diff --git a/frontend/rust-lib/lib-dispatch/Cargo.toml b/frontend/rust-lib/lib-dispatch/Cargo.toml index f81eb2d084..0d835915c7 100644 --- a/frontend/rust-lib/lib-dispatch/Cargo.toml +++ b/frontend/rust-lib/lib-dispatch/Cargo.toml @@ -42,7 +42,7 @@ tokio = { workspace = true, features = ["rt"] } futures-util = "0.3.26" [features] -default = ["use_protobuf"] +default = ["local_set", "use_protobuf"] use_serde = ["bincode", "serde_json", "serde", "serde_repr"] use_protobuf = ["protobuf"] local_set = [] diff --git a/frontend/rust-lib/lib-dispatch/src/dispatcher.rs b/frontend/rust-lib/lib-dispatch/src/dispatcher.rs index eb55bfc4fa..e3e72ff2be 100644 --- a/frontend/rust-lib/lib-dispatch/src/dispatcher.rs +++ b/frontend/rust-lib/lib-dispatch/src/dispatcher.rs @@ -1,10 +1,10 @@ -use std::any::Any; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{future::Future, sync::Arc}; - use derivative::*; use pin_project::pin_project; +use std::any::Any; +use std::future::Future; +use std::pin::Pin; +use std::rc::Rc; +use std::task::{Context, Poll}; use tracing::event; use crate::module::AFPluginStateMap; @@ -16,60 +16,50 @@ use crate::{ service::{AFPluginServiceFactory, Service}, }; -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub trait AFConcurrent {} -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] impl AFConcurrent for T where T: ?Sized {} -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub trait AFConcurrent: Send + Sync {} -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] impl AFConcurrent for T where T: Send + Sync {} -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub type AFBoxFuture<'a, T> = futures_core::future::LocalBoxFuture<'a, T>; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub type AFBoxFuture<'a, T> = futures_core::future::BoxFuture<'a, T>; pub type AFStateMap = std::sync::Arc; -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub(crate) fn downcast_owned(boxed: AFBox) -> Option { boxed.downcast().ok().map(|boxed| *boxed) } -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub(crate) fn downcast_owned(boxed: AFBox) -> Option { boxed.downcast().ok().map(|boxed| *boxed) } -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub(crate) type AFBox = Box; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub(crate) type AFBox = Box; -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub type BoxFutureCallback = Box AFBoxFuture<'static, ()> + 'static>; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub type BoxFutureCallback = Box AFBoxFuture<'static, ()> + Send + Sync + 'static>; -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] -pub fn af_spawn(future: T) -> tokio::task::JoinHandle -where - T: Future + 'static, - T::Output: 'static, -{ - tokio::task::spawn_local(future) -} - -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] pub fn af_spawn(future: T) -> tokio::task::JoinHandle where T: Future + Send + 'static, @@ -80,11 +70,11 @@ where pub struct AFPluginDispatcher { plugins: AFPluginMap, - runtime: Arc, + runtime: Rc, } impl AFPluginDispatcher { - pub fn new(runtime: Arc, plugins: Vec) -> AFPluginDispatcher { + pub fn new(runtime: Rc, plugins: Vec) -> AFPluginDispatcher { tracing::trace!("{}", plugin_info(&plugins)); AFPluginDispatcher { plugins: plugin_map_or_crash(plugins), @@ -126,14 +116,37 @@ impl AFPluginDispatcher { // The provided future will start running in the background immediately // when `spawn` is called, even if you don't await the returned // `JoinHandle`. - let handle = dispatch.runtime.spawn(async move { - service.call(service_ctx).await.unwrap_or_else(|e| { - tracing::error!("Dispatch runtime error: {:?}", e); - InternalError::Other(format!("{:?}", e)).as_response() - }) - }); + let result: Result; + #[cfg(feature = "local_set")] + { + let handle = dispatch.runtime.local.spawn_local(async move { + service.call(service_ctx).await.unwrap_or_else(|e| { + tracing::error!("Dispatch runtime error: {:?}", e); + InternalError::Other(format!("{:?}", e)).as_response() + }) + }); + + result = dispatch + .runtime + .local + .run_until(handle) + .await + .map_err(|e| e.to_string().into()) + } + + #[cfg(not(feature = "local_set"))] + { + result = dispatch + .runtime + .spawn(async move { + service.call(service_ctx).await.unwrap_or_else(|e| { + tracing::error!("Dispatch runtime error: {:?}", e); + InternalError::Other(format!("{:?}", e)).as_response() + }) + }) + .await; + } - let result = dispatch.runtime.run_until(handle).await; result.unwrap_or_else(|e| { let msg = format!("EVENT_DISPATCH join error: {:?}", e); tracing::error!("{}", msg); @@ -170,16 +183,17 @@ impl AFPluginDispatcher { callback: Some(Box::new(callback)), }; - let handle = dispatch.runtime.spawn(async move { - service.call(service_ctx).await.unwrap_or_else(|e| { - tracing::error!("[dispatch]: runtime error: {:?}", e); - InternalError::Other(format!("{:?}", e)).as_response() - }) - }); - - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] + #[cfg(feature = "local_set")] { - let result = dispatch.runtime.block_on(handle); + let handle = dispatch.runtime.local.spawn_local(async move { + service.call(service_ctx).await.unwrap_or_else(|e| { + tracing::error!("Dispatch runtime error: {:?}", e); + InternalError::Other(format!("{:?}", e)).as_response() + }) + }); + + let fut = dispatch.runtime.local.run_until(handle); + let result = dispatch.runtime.block_on(fut); DispatchFuture { fut: Box::pin(async move { result.unwrap_or_else(|e| { @@ -192,8 +206,18 @@ impl AFPluginDispatcher { } } - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] + #[cfg(not(feature = "local_set"))] { + let handle = dispatch.runtime.spawn(async move { + service + .call(crate::service::service::Service) + .await + .unwrap_or_else(|e| { + tracing::error!("[dispatch]: runtime error: {:?}", e); + InternalError::Other(format!("{:?}", e)).as_response() + }) + }); + let runtime = dispatch.runtime.clone(); DispatchFuture { fut: Box::pin(async move { @@ -211,7 +235,7 @@ impl AFPluginDispatcher { #[cfg(not(target_arch = "wasm32"))] pub fn sync_send( - dispatch: Arc, + dispatch: Rc, request: AFPluginRequest, ) -> AFPluginEventResponse { futures::executor::block_on(AFPluginDispatcher::async_send_with_callback( @@ -221,16 +245,6 @@ impl AFPluginDispatcher { )) } - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] - #[track_caller] - pub fn spawn(&self, future: F) -> tokio::task::JoinHandle - where - F: Future + 'static, - { - self.runtime.spawn(future) - } - - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] #[track_caller] pub fn spawn(&self, future: F) -> tokio::task::JoinHandle where @@ -239,24 +253,6 @@ impl AFPluginDispatcher { { self.runtime.spawn(future) } - - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] - pub async fn run_until(&self, future: F) -> F::Output - where - F: Future + 'static, - { - let handle = self.runtime.spawn(future); - self.runtime.run_until(handle).await.unwrap() - } - - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] - pub async fn run_until<'a, F>(&self, future: F) -> F::Output - where - F: Future + Send + 'a, - ::Output: Send + 'a, - { - self.runtime.run_until(future).await - } } #[derive(Derivative)] diff --git a/frontend/rust-lib/lib-dispatch/src/module/module.rs b/frontend/rust-lib/lib-dispatch/src/module/module.rs index a5b2df234a..ae92cf9a0c 100644 --- a/frontend/rust-lib/lib-dispatch/src/module/module.rs +++ b/frontend/rust-lib/lib-dispatch/src/module/module.rs @@ -1,3 +1,7 @@ +use futures_core::ready; +use nanoid::nanoid; +use pin_project::pin_project; +use std::rc::Rc; use std::sync::Arc; use std::{ collections::HashMap, @@ -9,10 +13,6 @@ use std::{ task::{Context, Poll}, }; -use futures_core::ready; -use nanoid::nanoid; -use pin_project::pin_project; - use crate::dispatcher::AFConcurrent; use crate::prelude::{AFBoxFuture, AFStateMap}; use crate::service::AFPluginHandler; @@ -26,12 +26,12 @@ use crate::{ }, }; -pub type AFPluginMap = Arc>>; +pub type AFPluginMap = Rc>>; pub(crate) fn plugin_map_or_crash(plugins: Vec) -> AFPluginMap { - let mut plugin_map: HashMap> = HashMap::new(); + let mut plugin_map: HashMap> = HashMap::new(); plugins.into_iter().for_each(|m| { let events = m.events(); - let plugins = Arc::new(m); + let plugins = Rc::new(m); events.into_iter().for_each(|e| { if plugin_map.contains_key(&e) { let plugin_name = plugin_map.get(&e).map(|p| &p.name); @@ -40,7 +40,7 @@ pub(crate) fn plugin_map_or_crash(plugins: Vec) -> AFPluginMap { plugin_map.insert(e, plugins.clone()); }); }); - Arc::new(plugin_map) + Rc::new(plugin_map) } #[derive(PartialEq, Eq, Hash, Debug, Clone)] @@ -67,7 +67,7 @@ pub struct AFPlugin { /// Contains a list of factories that are used to generate the services used to handle the passed-in /// `ServiceRequest`. /// - event_service_factory: Arc< + event_service_factory: Rc< HashMap>, >, } @@ -77,7 +77,7 @@ impl std::default::Default for AFPlugin { Self { name: "".to_owned(), states: Default::default(), - event_service_factory: Arc::new(HashMap::new()), + event_service_factory: Rc::new(HashMap::new()), } } } @@ -113,7 +113,7 @@ impl AFPlugin { if self.event_service_factory.contains_key(&event) { panic!("Register duplicate Event: {:?}", &event); } else { - Arc::get_mut(&mut self.event_service_factory) + Rc::get_mut(&mut self.event_service_factory) .unwrap() .insert(event, factory(AFPluginHandlerService::new(handler))); } @@ -185,7 +185,7 @@ impl AFPluginServiceFactory for AFPlugin { } pub struct AFPluginService { - services: Arc< + services: Rc< HashMap>, >, states: AFStateMap, diff --git a/frontend/rust-lib/lib-dispatch/src/runtime.rs b/frontend/rust-lib/lib-dispatch/src/runtime.rs index fd3658517c..eaa3223a20 100644 --- a/frontend/rust-lib/lib-dispatch/src/runtime.rs +++ b/frontend/rust-lib/lib-dispatch/src/runtime.rs @@ -8,8 +8,8 @@ use tokio::task::JoinHandle; pub struct AFPluginRuntime { inner: Runtime, - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] - local: tokio::task::LocalSet, + #[cfg(feature = "local_set")] + pub(crate) local: tokio::task::LocalSet, } impl Display for AFPluginRuntime { @@ -27,21 +27,11 @@ impl AFPluginRuntime { let inner = default_tokio_runtime()?; Ok(Self { inner, - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] + #[cfg(feature = "local_set")] local: tokio::task::LocalSet::new(), }) } - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] - #[track_caller] - pub fn spawn(&self, future: F) -> JoinHandle - where - F: Future + 'static, - { - self.local.spawn_local(future) - } - - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] #[track_caller] pub fn spawn(&self, future: F) -> JoinHandle where @@ -51,23 +41,7 @@ impl AFPluginRuntime { self.inner.spawn(future) } - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] - pub async fn run_until(&self, future: F) -> F::Output - where - F: Future, - { - self.local.run_until(future).await - } - - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] - pub async fn run_until(&self, future: F) -> F::Output - where - F: Future, - { - future.await - } - - #[cfg(any(target_arch = "wasm32", feature = "local_set"))] + #[cfg(feature = "local_set")] #[track_caller] pub fn block_on(&self, f: F) -> F::Output where @@ -76,7 +50,7 @@ impl AFPluginRuntime { self.local.block_on(&self.inner, f) } - #[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] + #[cfg(not(feature = "local_set"))] #[track_caller] pub fn block_on(&self, f: F) -> F::Output where @@ -86,14 +60,26 @@ impl AFPluginRuntime { } } -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub fn default_tokio_runtime() -> io::Result { - runtime::Builder::new_current_thread() - .thread_name("dispatch-rt-st") - .build() + #[cfg(not(target_arch = "wasm32"))] + { + runtime::Builder::new_multi_thread() + .enable_io() + .enable_time() + .thread_name("dispatch-rt-st") + .build() + } + + #[cfg(target_arch = "wasm32")] + { + runtime::Builder::new_current_thread() + .thread_name("dispatch-rt-st") + .build() + } } -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub fn default_tokio_runtime() -> io::Result { runtime::Builder::new_multi_thread() .thread_name("dispatch-rt-mt") diff --git a/frontend/rust-lib/lib-dispatch/src/service/boxed.rs b/frontend/rust-lib/lib-dispatch/src/service/boxed.rs index 7ff7a7c116..811b995082 100644 --- a/frontend/rust-lib/lib-dispatch/src/service/boxed.rs +++ b/frontend/rust-lib/lib-dispatch/src/service/boxed.rs @@ -16,7 +16,7 @@ where BoxServiceFactory(Box::new(FactoryWrapper(factory))) } -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] type Inner = Box< dyn AFPluginServiceFactory< Req, @@ -27,7 +27,7 @@ type Inner = Box< Future = AFBoxFuture<'static, Result, Err>>, >, >; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] type Inner = Box< dyn AFPluginServiceFactory< Req, @@ -58,12 +58,12 @@ where } } -#[cfg(any(target_arch = "wasm32", feature = "local_set"))] +#[cfg(feature = "local_set")] pub type BoxService = Box< dyn Service>>, >; -#[cfg(all(not(target_arch = "wasm32"), not(feature = "local_set")))] +#[cfg(not(feature = "local_set"))] pub type BoxService = Box< dyn Service>> + Sync diff --git a/frontend/rust-lib/lib-dispatch/tests/api/module.rs b/frontend/rust-lib/lib-dispatch/tests/api/module.rs index 2c4539bd7e..fed8d75720 100644 --- a/frontend/rust-lib/lib-dispatch/tests/api/module.rs +++ b/frontend/rust-lib/lib-dispatch/tests/api/module.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::rc::Rc; use lib_dispatch::prelude::*; use lib_dispatch::runtime::AFPluginRuntime; @@ -10,8 +10,8 @@ pub async fn hello() -> String { #[tokio::test] async fn test() { let event = "1"; - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); - let dispatch = Arc::new(AFPluginDispatcher::new( + let runtime = Rc::new(AFPluginRuntime::new().unwrap()); + let dispatch = Rc::new(AFPluginDispatcher::new( runtime, vec![AFPlugin::new().event(event, hello)], ));