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 77e68803f9..9083e40521 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 @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +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/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; 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 da8d9cd2fe..402243516d 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 @@ -1,13 +1,11 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:ffi'; -import 'dart:isolate'; +import 'package:appflowy/plugins/ai_chat/application/chat_message_stream.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:appflowy_backend/protobuf/flowy-error/code.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; @@ -18,18 +16,12 @@ import 'package:flutter_chat_types/flutter_chat_types.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:nanoid/nanoid.dart'; +import 'chat_entity.dart'; import 'chat_message_listener.dart'; import 'chat_message_service.dart'; -part 'chat_bloc.g.dart'; part 'chat_bloc.freezed.dart'; -const sendMessageErrorKey = "sendMessageError"; -const systemUserId = "system"; -const aiResponseUserId = "0"; -const messageMetadataKey = "metadata"; -const messageQuestionIdKey = "question"; - class ChatBloc extends Bloc { ChatBloc({ required ViewPB view, @@ -100,7 +92,7 @@ class ChatBloc extends Bloc { _loadPrevMessage(beforeMessageId); emit( state.copyWith( - loadingPreviousStatus: const LoadingState.loading(), + loadingPreviousStatus: const ChatLoadingState.loading(), ), ); }, @@ -116,7 +108,7 @@ class ChatBloc extends Bloc { emit( state.copyWith( messages: uniqueMessages, - loadingPreviousStatus: const LoadingState.finish(), + loadingPreviousStatus: const ChatLoadingState.finish(), hasMorePrevMessage: hasMore, ), ); @@ -130,7 +122,7 @@ class ChatBloc extends Bloc { emit( state.copyWith( messages: uniqueMessages, - initialLoadingStatus: const LoadingState.finish(), + initialLoadingStatus: const ChatLoadingState.finish(), ), ); }, @@ -200,6 +192,14 @@ class ChatBloc extends Bloc { 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, @@ -406,7 +406,6 @@ class ChatBloc extends Bloc { Message _createStreamMessage(AnswerStream stream, Int64 questionMessageId) { final streamMessageId = (questionMessageId + 1).toString(); - lastStreamMessageId = streamMessageId; return TextMessage( @@ -488,10 +487,10 @@ class ChatState with _$ChatState { required UserProfilePB userProfile, // When opening the chat, the initial loading status will be set as loading. //After the initial loading is done, the status will be set as finished. - required LoadingState initialLoadingStatus, + required ChatLoadingState initialLoadingStatus, // When loading previous messages, the status will be set as loading. // After the loading is done, the status will be set as finished. - required LoadingState loadingPreviousStatus, + required ChatLoadingState loadingPreviousStatus, // When sending a user message, the status will be set as loading. // After the message is sent, the status will be set as finished. required StreamingState streamingState, @@ -511,8 +510,8 @@ class ChatState with _$ChatState { view: view, messages: [], userProfile: userProfile, - initialLoadingStatus: const LoadingState.finish(), - loadingPreviousStatus: const LoadingState.finish(), + initialLoadingStatus: const ChatLoadingState.finish(), + loadingPreviousStatus: const ChatLoadingState.finish(), streamingState: const StreamingState.done(), sendingState: const SendMessageState.done(), hasMorePrevMessage: true, @@ -525,169 +524,3 @@ bool isOtherUserMessage(Message message) { message.author.id != systemUserId && !message.author.id.startsWith("streamId:"); } - -@freezed -class LoadingState with _$LoadingState { - const factory LoadingState.loading() = _Loading; - const factory LoadingState.finish({FlowyError? error}) = _Finish; -} - -enum OnetimeShotType { - unknown, - sendingMessage, - relatedQuestion, - invalidSendMesssage, -} - -const onetimeShotType = "OnetimeShotType"; - -extension OnetimeMessageTypeExtension on OnetimeShotType { - static OnetimeShotType fromString(String value) { - switch (value) { - case 'OnetimeShotType.relatedQuestion': - return OnetimeShotType.relatedQuestion; - case 'OnetimeShotType.invalidSendMesssage': - return OnetimeShotType.invalidSendMesssage; - default: - Log.error('Unknown OnetimeShotType: $value'); - return OnetimeShotType.unknown; - } - } - - Map toMap() { - return { - onetimeShotType: toString(), - }; - } -} - -OnetimeShotType? onetimeMessageTypeFromMeta(Map? metadata) { - if (metadata == null) { - return null; - } - - for (final entry in metadata.entries) { - if (entry.key == onetimeShotType) { - return OnetimeMessageTypeExtension.fromString(entry.value as String); - } - } - return null; -} - -class AnswerStream { - AnswerStream() { - _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("error:")) { - _error = event.substring(5); - if (_onError != null) { - _onError!(_error!); - } - } else if (event.startsWith("metadata:")) { - if (_onMetadata != null) { - final s = event.substring(9); - _onMetadata!(messageRefSourceFromString(s)); - } - } else if (event == "AI_RESPONSE_LIMIT") { - if (_onAIResponseLimit != null) { - _onAIResponseLimit!(); - } - } - }, - onDone: () { - if (_onEnd != null) { - _onEnd!(); - } - }, - 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()? _onStart; - void Function()? _onEnd; - void Function(String error)? _onError; - void Function()? _onAIResponseLimit; - void Function(List metadata)? _onMetadata; - - 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()? onStart, - void Function()? onEnd, - void Function(String error)? onError, - void Function()? onAIResponseLimit, - void Function(List metadata)? onMetadata, - }) { - _onData = onData; - _onStart = onStart; - _onEnd = onEnd; - _onError = onError; - _onAIResponseLimit = onAIResponseLimit; - _onMetadata = onMetadata; - - if (_onStart != null) { - _onStart!(); - } - } -} - -@JsonSerializable() -class ChatMessageRefSource { - ChatMessageRefSource({ - required this.id, - required this.name, - required this.source, - }); - - factory ChatMessageRefSource.fromJson(Map json) => - _$ChatMessageRefSourceFromJson(json); - - final String id; - final String name; - final String source; - - Map toJson() => _$ChatMessageRefSourceToJson(this); -} - -@freezed -class StreamingState with _$StreamingState { - const factory StreamingState.streaming() = _Streaming; - const factory StreamingState.done({FlowyError? error}) = _StreamDone; -} - -@freezed -class SendMessageState with _$SendMessageState { - const factory SendMessageState.sending() = _Sending; - const factory SendMessageState.done({FlowyError? error}) = _SendDone; -} 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 new file mode 100644 index 0000000000..99b6b66bb1 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart @@ -0,0 +1,176 @@ +import 'dart:io'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:path/path.dart' as path; + +part 'chat_entity.g.dart'; +part 'chat_entity.freezed.dart'; + +const sendMessageErrorKey = "sendMessageError"; +const systemUserId = "system"; +const aiResponseUserId = "0"; +const messageMetadataKey = "metadata"; +const messageQuestionIdKey = "question"; + +@JsonSerializable() +class ChatMessageRefSource { + ChatMessageRefSource({ + required this.id, + required this.name, + required this.source, + }); + + factory ChatMessageRefSource.fromJson(Map json) => + _$ChatMessageRefSourceFromJson(json); + + final String id; + final String name; + final String source; + + Map toJson() => _$ChatMessageRefSourceToJson(this); +} + +@freezed +class StreamingState with _$StreamingState { + const factory StreamingState.streaming() = _Streaming; + const factory StreamingState.done({FlowyError? error}) = _StreamDone; +} + +@freezed +class SendMessageState with _$SendMessageState { + const factory SendMessageState.sending() = _Sending; + const factory SendMessageState.done({FlowyError? error}) = _SendDone; +} + +class ChatFile extends Equatable { + const ChatFile({ + required this.filePath, + required this.fileName, + required this.fileType, + }); + + static ChatFile? fromFilePath(String filePath) { + final file = File(filePath); + if (!file.existsSync()) { + return null; + } + + final fileName = path.basename(filePath); + final extension = path.extension(filePath).toLowerCase(); + + ChatMessageMetaTypePB fileType; + switch (extension) { + case '.pdf': + fileType = ChatMessageMetaTypePB.PDF; + break; + case '.txt': + fileType = ChatMessageMetaTypePB.Txt; + break; + case '.md': + fileType = ChatMessageMetaTypePB.Markdown; + break; + default: + fileType = ChatMessageMetaTypePB.UnknownMetaType; + } + + return ChatFile( + filePath: filePath, + fileName: fileName, + fileType: fileType, + ); + } + + final String filePath; + final String fileName; + final ChatMessageMetaTypePB fileType; + + @override + List get props => [filePath]; +} + +extension ChatFileTypeExtension on ChatMessageMetaTypePB { + Widget get icon { + switch (this) { + case ChatMessageMetaTypePB.PDF: + return const FlowySvg( + FlowySvgs.file_pdf_s, + color: Color(0xff00BCF0), + ); + case ChatMessageMetaTypePB.Txt: + return const FlowySvg( + FlowySvgs.file_txt_s, + color: Color(0xff00BCF0), + ); + case ChatMessageMetaTypePB.Markdown: + return const FlowySvg( + FlowySvgs.file_md_s, + color: Color(0xff00BCF0), + ); + default: + return const FlowySvg(FlowySvgs.file_unknown_s); + } + } +} + +typedef ChatInputFileMetadata = Map; + +@freezed +class ChatLoadingState with _$ChatLoadingState { + const factory ChatLoadingState.loading() = _Loading; + const factory ChatLoadingState.finish({FlowyError? error}) = _Finish; +} + +extension ChatLoadingStateExtension on ChatLoadingState { + bool get isLoading => this is _Loading; + bool get isFinish => this is _Finish; +} + +enum OnetimeShotType { + unknown, + sendingMessage, + relatedQuestion, + invalidSendMesssage, +} + +const onetimeShotType = "OnetimeShotType"; + +extension OnetimeMessageTypeExtension on OnetimeShotType { + static OnetimeShotType fromString(String value) { + switch (value) { + case 'OnetimeShotType.sendingMessage': + return OnetimeShotType.sendingMessage; + case 'OnetimeShotType.relatedQuestion': + return OnetimeShotType.relatedQuestion; + case 'OnetimeShotType.invalidSendMesssage': + return OnetimeShotType.invalidSendMesssage; + default: + Log.error('Unknown OnetimeShotType: $value'); + return OnetimeShotType.unknown; + } + } + + Map toMap() { + return { + onetimeShotType: toString(), + }; + } +} + +OnetimeShotType? onetimeMessageTypeFromMeta(Map? metadata) { + if (metadata == null) { + return null; + } + + for (final entry in metadata.entries) { + if (entry.key == onetimeShotType) { + return OnetimeMessageTypeExtension.fromString(entry.value as String); + } + } + return null; +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_file_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_file_bloc.dart index 4d05e27fd5..a01c3b32d5 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_file_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_file_bloc.dart @@ -1,23 +1,17 @@ import 'dart:async'; -import 'dart:io'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.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:equatable/equatable.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:path/path.dart' as path; import 'chat_input_bloc.dart'; part 'chat_file_bloc.freezed.dart'; -typedef ChatInputFileMetadata = Map; - class ChatFileBloc extends Bloc { ChatFileBloc() : listener = LocalLLMListener(), @@ -158,64 +152,3 @@ class ChatFileState with _$ChatFileState { @Default(AIType.appflowyAI()) AIType aiType, }) = _ChatFileState; } - -class ChatFile extends Equatable { - const ChatFile({ - required this.filePath, - required this.fileName, - required this.fileType, - }); - - static ChatFile? fromFilePath(String filePath) { - final file = File(filePath); - if (!file.existsSync()) { - return null; - } - - final fileName = path.basename(filePath); - final extension = path.extension(filePath).toLowerCase(); - - ChatMessageMetaTypePB fileType; - switch (extension) { - case '.pdf': - fileType = ChatMessageMetaTypePB.PDF; - break; - case '.txt': - fileType = ChatMessageMetaTypePB.Txt; - break; - case '.md': - fileType = ChatMessageMetaTypePB.Markdown; - break; - default: - fileType = ChatMessageMetaTypePB.UnknownMetaType; - } - - return ChatFile( - filePath: filePath, - fileName: fileName, - fileType: fileType, - ); - } - - final String filePath; - final String fileName; - final ChatMessageMetaTypePB fileType; - - @override - List get props => [filePath]; -} - -extension ChatFileTypeExtension on ChatMessageMetaTypePB { - Widget get icon { - switch (this) { - case ChatMessageMetaTypePB.PDF: - return const FlowySvg(FlowySvgs.file_pdf_s); - case ChatMessageMetaTypePB.Txt: - return const FlowySvg(FlowySvgs.file_txt_s); - case ChatMessageMetaTypePB.Markdown: - return const FlowySvg(FlowySvgs.file_md_s); - default: - return const FlowySvg(FlowySvgs.file_unknown_s); - } - } -} 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 ff67ab2019..6f8877497f 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,6 +1,6 @@ import 'dart:async'; -import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; +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'; 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 71fa3a18d2..a2cfc9f986 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 @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_action_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; @@ -10,8 +10,6 @@ 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'; -import 'chat_file_bloc.dart'; - List fileListFromMessageMetadata( Map? map, ) { @@ -117,7 +115,7 @@ Future> metadataPBFromMetadata( name: view.name, data: pb.text, dataType: ChatMessageMetaTypePB.Txt, - source: "appflowy document", + source: "appflowy", ), ); }, (err) { 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 new file mode 100644 index 0000000000..3124e08c77 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart @@ -0,0 +1,94 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_message_service.dart'; + +class AnswerStream { + AnswerStream() { + _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("error:")) { + _error = event.substring(5); + if (_onError != null) { + _onError!(_error!); + } + } else if (event.startsWith("metadata:")) { + if (_onMetadata != null) { + final s = event.substring(9); + _onMetadata!(messageRefSourceFromString(s)); + } + } else if (event == "AI_RESPONSE_LIMIT") { + if (_onAIResponseLimit != null) { + _onAIResponseLimit!(); + } + } + }, + onDone: () { + if (_onEnd != null) { + _onEnd!(); + } + }, + 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()? _onStart; + void Function()? _onEnd; + void Function(String error)? _onError; + void Function()? _onAIResponseLimit; + void Function(List metadata)? _onMetadata; + + 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()? onStart, + void Function()? onEnd, + void Function(String error)? onError, + void Function()? onAIResponseLimit, + void Function(List metadata)? onMetadata, + }) { + _onData = onData; + _onStart = onStart; + _onEnd = onEnd; + _onError = onError; + _onAIResponseLimit = onAIResponseLimit; + _onMetadata = onMetadata; + + if (_onStart != null) { + _onStart!(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_side_pannel_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_side_pannel_bloc.dart index fe0439f918..83dc4375b0 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_side_pannel_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_side_pannel_bloc.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; 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 a436936344..41fea4ab22 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,8 +1,8 @@ +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_file_bloc.dart'; import 'chat_message_service.dart'; part 'chat_user_message_bloc.freezed.dart'; @@ -10,7 +10,7 @@ part 'chat_user_message_bloc.freezed.dart'; class ChatUserMessageBloc extends Bloc { ChatUserMessageBloc({ - required TextMessage message, + required Message message, required String? metadata, }) : super( ChatUserMessageState.initial( @@ -36,12 +36,12 @@ class ChatUserMessageEvent with _$ChatUserMessageEvent { @freezed class ChatUserMessageState with _$ChatUserMessageState { const factory ChatUserMessageState({ - required TextMessage message, + required Message message, required List files, }) = _ChatUserMessageState; factory ChatUserMessageState.initial( - TextMessage message, + Message message, List files, ) => ChatUserMessageState(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 f7882eb351..a2a0618e41 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -2,8 +2,10 @@ import 'dart:math'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_message_stream.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_related_question.dart'; import 'package:appflowy/plugins/ai_chat/presentation/message/ai_message_bubble.dart'; import 'package:appflowy/plugins/ai_chat/presentation/message/other_user_message_bubble.dart'; @@ -256,27 +258,26 @@ class _ChatContentPageState extends State<_ChatContentPage> { theme: buildTheme(context), onEndReached: () async { if (state.hasMorePrevMessage && - state.loadingPreviousStatus != const LoadingState.loading()) { + state.loadingPreviousStatus.isFinish) { blocContext .read() .add(const ChatEvent.startLoadingPrevMessage()); } }, emptyState: BlocBuilder( - builder: (_, state) => - state.initialLoadingStatus == const LoadingState.finish() - ? Padding( - padding: AIChatUILayout.welcomePagePadding, - child: ChatWelcomePage( - userProfile: widget.userProfile, - onSelectedQuestion: (question) => blocContext - .read() - .add(ChatEvent.sendMessage(message: question)), - ), - ) - : const Center( - child: CircularProgressIndicator.adaptive(), - ), + builder: (_, state) => state.initialLoadingStatus.isFinish + ? Padding( + padding: AIChatUILayout.welcomePagePadding, + child: ChatWelcomePage( + userProfile: widget.userProfile, + onSelectedQuestion: (question) => blocContext + .read() + .add(ChatEvent.sendMessage(message: question)), + ), + ) + : const Center( + child: CircularProgressIndicator.adaptive(), + ), ), messageWidthRatio: AIChatUILayout.messageWidthRatio, textMessageBuilder: ( 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 4bea505e79..9f68586757 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 @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_action_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_action_control.dart'; @@ -115,7 +116,7 @@ class _ChatInputState extends State { border: Border.all( color: _inputFocusNode.hasFocus && !isMobile ? Theme.of(context).colorScheme.primary.withOpacity(0.6) - : Colors.grey.shade700, + : Theme.of(context).colorScheme.secondary, ), borderRadius: borderRadius, ), @@ -160,9 +161,8 @@ class _ChatInputState extends State { Expanded(child: _inputTextField(context, textPadding)), // at button - if (PlatformExtension.isDesktop && - widget.aiType == const AIType.appflowyAI()) - _atButton(buttonPadding), + // TODO(lucas): support mobile + if (PlatformExtension.isDesktop) _atButton(buttonPadding), // send button _sendButton(buttonPadding), @@ -267,10 +267,6 @@ class _ChatInputState extends State { } Future _handleOnTextChange(BuildContext context, String text) async { - if (widget.aiType != const AIType.appflowyAI()) { - return; - } - if (!_inputActionControl.onTextChanged(text)) { return; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart index 87bef8044b..ba6170a69a 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart @@ -1,5 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_file_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; @@ -67,17 +67,18 @@ class ChatFilePreview extends StatelessWidget { decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(4), + borderRadius: BorderRadius.circular(8), ), child: Stack( clipBehavior: Clip.none, children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 10, + horizontal: 10.0, + vertical: 14, ), child: Row( + mainAxisSize: MainAxisSize.min, children: [ file.fileType.icon, const HSpace(6), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_user_invalid_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_user_invalid_message.dart index 8ae9b91d32..39ea8c29b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_user_invalid_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_user_invalid_message.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart index 3e29af1a62..d13cd94071 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_input/chat_input.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_popmenu.dart'; 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 5cf9cee2f5..77ee0b5e76 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,5 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.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'; 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 582f587049..eaf746cae0 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 @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_loading.dart'; import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart'; import 'package:easy_localization/easy_localization.dart'; 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 e0cdc45562..6f60442a16 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,16 +1,12 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -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_member_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bloc.dart'; import 'package:appflowy/plugins/ai_chat/presentation/chat_avatar.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_types/flutter_chat_types.dart'; -import 'package:styled_widget/styled_widget.dart'; class ChatUserMessageBubble extends StatelessWidget { const ChatUserMessageBubble({ @@ -33,141 +29,130 @@ class ChatUserMessageBubble extends StatelessWidget { .read() .add(ChatMemberEvent.getMemberInfo(message.author.id)); } + final metadata = message.metadata?[messageMetadataKey] as String?; - return BlocConsumer( - listenWhen: (previous, current) { - return previous.members[message.author.id] != - current.members[message.author.id]; - }, - listener: (context, state) {}, - builder: (context, state) { - final member = state.members[message.author.id]; - return Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // _wrapHover( - Flexible( - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: borderRadius, - color: backgroundColor, + return BlocProvider( + create: (context) => ChatUserMessageBloc( + message: message, + metadata: metadata, + ), + child: BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (state.files.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.only(right: defaultAvatarSize + 32), + child: _MessageFileList(files: state.files), ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, + const VSpace(6), + ], + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: borderRadius, + color: backgroundColor, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + child: child, + ), + ), ), - child: child, - ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: BlocConsumer( + listenWhen: (previous, current) => + previous.members[message.author.id] != + current.members[message.author.id], + listener: (context, state) {}, + builder: (context, state) { + final member = state.members[message.author.id]; + return ChatUserAvatar( + iconUrl: member?.info.avatarUrl ?? "", + name: member?.info.name ?? "", + ); + }, + ), + ), + ], ), - ), - // ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ChatUserAvatar( - iconUrl: member?.info.avatarUrl ?? "", - name: member?.info.name ?? "", + ], + ); + }, + ), + ); + } +} + +class _MessageFileList extends StatelessWidget { + const _MessageFileList({required this.files}); + + final List files; + + @override + Widget build(BuildContext context) { + final List children = files + .map( + (file) => _MessageFile( + file: file, + ), + ) + .toList(); + + return Wrap( + direction: Axis.vertical, + crossAxisAlignment: WrapCrossAlignment.end, + spacing: 6, + runSpacing: 6, + children: children, + ); + } +} + +class _MessageFile extends StatelessWidget { + const _MessageFile({required this.file}); + + final ChatFile file; + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Theme.of(context).colorScheme.secondary, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 16), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox.square(dimension: 16, child: file.fileType.icon), + const HSpace(6), + Flexible( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 400), + child: FlowyText( + file.fileName, + fontSize: 12, + maxLines: 6, + ), ), ), ], - ); - }, - ); - } -} - -class ChatUserMessageHover extends StatefulWidget { - const ChatUserMessageHover({ - super.key, - required this.child, - required this.message, - }); - - final Widget child; - final Message message; - final bool autoShowHover = true; - - @override - State createState() => _ChatUserMessageHoverState(); -} - -class _ChatUserMessageHoverState extends State { - bool _isHover = false; - - @override - void initState() { - super.initState(); - _isHover = widget.autoShowHover ? false : true; - } - - @override - Widget build(BuildContext context) { - final List children = [ - DecoratedBox( - decoration: const BoxDecoration( - color: Colors.transparent, - borderRadius: Corners.s6Border, ), - child: Padding( - padding: const EdgeInsets.only(bottom: 30), - child: widget.child, - ), - ), - ]; - - if (_isHover) { - if (widget.message is TextMessage) { - children.add( - EditButton( - textMessage: widget.message as TextMessage, - ).positioned(right: 0, bottom: 0), - ); - } - } - - return MouseRegion( - cursor: SystemMouseCursors.click, - opaque: false, - onEnter: (p) => setState(() { - if (widget.autoShowHover) { - _isHover = true; - } - }), - onExit: (p) => setState(() { - if (widget.autoShowHover) { - _isHover = false; - } - }), - child: Stack( - alignment: AlignmentDirectional.centerStart, - children: children, - ), - ); - } -} - -class EditButton extends StatelessWidget { - const EditButton({ - super.key, - required this.textMessage, - }); - final TextMessage textMessage; - - @override - Widget build(BuildContext context) { - return FlowyTooltip( - message: LocaleKeys.settings_menu_clickToCopy.tr(), - child: FlowyIconButton( - width: 24, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - fillColor: Theme.of(context).cardColor, - icon: FlowySvg( - FlowySvgs.ai_copy_s, - size: const Size.square(14), - color: Theme.of(context).colorScheme.primary, - ), - onPressed: () {}, ), ); } 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 e9bf1928ac..3e37b12dd3 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,10 +1,6 @@ -import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; -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/widget/spacing.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 { @@ -23,28 +19,8 @@ class ChatUserTextMessageWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (context) => ChatUserMessageBloc( - message: message, - metadata: metadata, - ), - child: BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (state.files.isNotEmpty) ...[ - _MessageFileList(files: state.files), - const VSpace(6), - ], - TextMessageText( - text: message.text, - ), - ], - ); - }, - ), + return TextMessageText( + text: message.text, ); } } @@ -71,59 +47,3 @@ class TextMessageText extends StatelessWidget { ); } } - -class _MessageFileList extends StatelessWidget { - const _MessageFileList({required this.files}); - - final List files; - - @override - Widget build(BuildContext context) { - final List children = files - .map( - (file) => _MessageFile( - file: file, - ), - ) - .toList(); - - return Wrap( - spacing: 6, - runSpacing: 6, - children: children, - ); - } -} - -class _MessageFile extends StatelessWidget { - const _MessageFile({required this.file}); - - final ChatFile file; - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(4), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - file.fileType.icon, - const HSpace(6), - Flexible( - child: FlowyText( - file.fileName, - fontSize: 12, - maxLines: 6, - ), - ), - ], - ), - ), - ); - } -} 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 fdc3e8e3cd..a14206e38e 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,7 +2,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.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'; @@ -52,12 +52,16 @@ class DownloadModelBloc extends Bloc { emit( state.copyWith( downloadStream: downloadStream, - loadingState: const LoadingState.finish(), + loadingState: const ChatLoadingState.finish(), downloadError: null, ), ); }, (err) { - emit(state.copyWith(loadingState: LoadingState.finish(error: err))); + emit( + state.copyWith( + loadingState: ChatLoadingState.finish(error: err), + ), + ); }); }, updatePercent: (String object, double percent) { @@ -95,7 +99,7 @@ class DownloadModelState with _$DownloadModelState { @Default("") String object, @Default(0) double percent, @Default(false) bool isFinish, - @Default(LoadingState.loading()) LoadingState loadingState, + @Default(ChatLoadingState.loading()) ChatLoadingState loadingState, }) = _DownloadModelState; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart index 03d4202027..7f1df258ea 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; @@ -72,14 +72,14 @@ class LocalAIChatSettingBloc llmResource, llmModel, ), - selectLLMState: const LoadingState.finish(), + selectLLMState: const ChatLoadingState.finish(), ), ); } else { emit( state.copyWith( selectedLLMModel: llmModel, - selectLLMState: const LoadingState.finish(), + selectLLMState: const ChatLoadingState.finish(), progressIndicator: const LocalAIProgress.checkPluginState(), ), ); @@ -88,7 +88,7 @@ class LocalAIChatSettingBloc (err) { emit( state.copyWith( - selectLLMState: LoadingState.finish(error: err), + selectLLMState: ChatLoadingState.finish(error: err), ), ); }, @@ -117,7 +117,7 @@ class LocalAIChatSettingBloc state.copyWith( progressIndicator: LocalAIProgress.startDownloading(state.selectedLLMModel!), - selectLLMState: const LoadingState.finish(), + selectLLMState: const ChatLoadingState.finish(), ), ); return; @@ -128,7 +128,7 @@ class LocalAIChatSettingBloc llmResource, state.selectedLLMModel!, ), - selectLLMState: const LoadingState.finish(), + selectLLMState: const ChatLoadingState.finish(), ), ); } @@ -139,7 +139,7 @@ class LocalAIChatSettingBloc emit( state.copyWith( progressIndicator: LocalAIProgress.startDownloading(llmModel), - selectLLMState: const LoadingState.finish(), + selectLLMState: const ChatLoadingState.finish(), ), ); }, @@ -257,7 +257,7 @@ class LocalAIChatSettingState with _$LocalAIChatSettingState { LLMModelPB? selectedLLMModel, LocalAIProgress? progressIndicator, @Default(AIModelProgress.init()) AIModelProgress aiModelProgress, - @Default(LoadingState.loading()) LoadingState selectLLMState, + @Default(ChatLoadingState.loading()) ChatLoadingState selectLLMState, @Default([]) List models, @Default(RunningStatePB.Connecting) RunningStatePB runningState, }) = _LocalAIChatSettingState; diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart index 3be0636d07..18bc296c39 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart @@ -118,6 +118,10 @@ extension ViewExtension on ViewPB { bool get isSpace { try { + if (extra.isEmpty) { + return false; + } + final ext = jsonDecode(extra); final isSpace = ext[ViewExtKeys.isSpaceKey] ?? false; return isSpace; @@ -138,6 +142,10 @@ extension ViewExtension on ViewPB { FlowySvg? buildSpaceIconSvg(BuildContext context, {Size? size}) { try { + if (extra.isEmpty) { + return null; + } + final ext = jsonDecode(extra); final icon = ext[ViewExtKeys.spaceIconKey]; final color = ext[ViewExtKeys.spaceIconColorKey]; @@ -214,6 +222,11 @@ extension ViewExtension on ViewPB { if (layout != ViewLayoutPB.Document) { return null; } + + if (extra.isEmpty) { + return null; + } + try { final ext = jsonDecode(extra); final cover = ext[ViewExtKeys.coverKey] ?? {}; diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 9e99c50cd7..692ea6eb43 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" dependencies = [ "anyhow", "cfg-if", @@ -315,9 +315,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -423,7 +423,7 @@ dependencies = [ "bitflags 2.4.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.12.1", "lazy_static", "lazycell", "proc-macro2", @@ -826,7 +826,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "async-trait", @@ -1532,7 +1532,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "app-error", diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index d9279126c8..b91db1138a 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 = "99410fb7662440e75493df110de2283f75ab2418" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } [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 = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } +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" } diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock index b77a35c7a1..ae08a44db4 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" dependencies = [ "anyhow", "cfg-if", @@ -325,9 +325,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.79" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "async-trait", @@ -1522,7 +1522,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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 b157b83731..59cc7abffb 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 = "99410fb7662440e75493df110de2283f75ab2418" } +client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "135a67dc79848c39e9c53b4a99b6d14f444686ef" } [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 = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } +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" } diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index 253255f99d..ae4b034439 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" 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=7dd879a6b9b3246e5cd06f1647e620553db9b960#7dd879a6b9b3246e5cd06f1647e620553db9b960" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=65802795ad8778de11c45b5af65d05c973709613#65802795ad8778de11c45b5af65d05c973709613" dependencies = [ "anyhow", "cfg-if", @@ -289,9 +289,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", @@ -718,7 +718,7 @@ dependencies = [ [[package]] name = "client-api" version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" 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=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "async-trait", @@ -1256,7 +1256,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf 0.8.0", + "phf 0.11.2", "smallvec", ] @@ -1356,7 +1356,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "database-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "app-error", @@ -2730,7 +2730,7 @@ dependencies = [ [[package]] name = "gotrue" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "futures-util", @@ -2747,7 +2747,7 @@ dependencies = [ [[package]] name = "gotrue-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "app-error", @@ -3112,7 +3112,7 @@ dependencies = [ [[package]] name = "infra" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "bytes", @@ -4068,7 +4068,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros", + "phf_macros 0.8.0", "phf_shared 0.8.0", "proc-macro-hack", ] @@ -4088,6 +4088,7 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ + "phf_macros 0.11.2", "phf_shared 0.11.2", ] @@ -4155,6 +4156,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.47", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -5307,7 +5321,7 @@ dependencies = [ [[package]] name = "shared-entity" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=99410fb7662440e75493df110de2283f75ab2418#99410fb7662440e75493df110de2283f75ab2418" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=135a67dc79848c39e9c53b4a99b6d14f444686ef#135a67dc79848c39e9c53b4a99b6d14f444686ef" dependencies = [ "anyhow", "app-error", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 828b04d84d..a6aebe740b 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -80,7 +80,7 @@ parking_lot = "0.12" futures = "0.3.29" tokio = "1.38.0" tokio-stream = "0.1.14" -async-trait = "0.1.74" +async-trait = "0.1.81" chrono = { version = "0.4.31", default-features = false, features = ["clock"] } collab = { version = "0.2" } collab-entity = { version = "0.2" } @@ -99,8 +99,8 @@ zip = "2.1.3" # 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 = "99410fb7662440e75493df110de2283f75ab2418" } -client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "99410fb7662440e75493df110de2283f75ab2418" } +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" } [profile.dev] opt-level = 0 @@ -147,5 +147,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 = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "7dd879a6b9b3246e5cd06f1647e620553db9b960" } +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" } diff --git a/frontend/rust-lib/event-integration-test/tests/chat/chat_message_test.rs b/frontend/rust-lib/event-integration-test/tests/chat/chat_message_test.rs index f6217f8d38..9b3396b036 100644 --- a/frontend/rust-lib/event-integration-test/tests/chat/chat_message_test.rs +++ b/frontend/rust-lib/event-integration-test/tests/chat/chat_message_test.rs @@ -25,7 +25,7 @@ async fn af_cloud_create_chat_message_test() { &chat_id, &format!("hello world {}", i), ChatMessageType::System, - vec![], + &[], ) .await .unwrap(); @@ -81,7 +81,7 @@ async fn af_cloud_load_remote_system_message_test() { &chat_id, &format!("hello server {}", i), ChatMessageType::System, - vec![], + &[], ) .await .unwrap(); diff --git a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs index 520ea640e6..5096c8b981 100644 --- a/frontend/rust-lib/flowy-ai-pub/src/cloud.rs +++ b/frontend/rust-lib/flowy-ai-pub/src/cloud.rs @@ -26,14 +26,14 @@ pub trait ChatCloudService: Send + Sync + 'static { chat_id: &str, ) -> FutureResult<(), FlowyError>; - fn create_question( + async fn create_question( &self, workspace_id: &str, chat_id: &str, message: &str, message_type: ChatMessageType, - metadata: Vec, - ) -> FutureResult; + metadata: &[ChatMessageMetadata], + ) -> Result; fn create_answer( &self, diff --git a/frontend/rust-lib/flowy-ai/src/ai_manager.rs b/frontend/rust-lib/flowy-ai/src/ai_manager.rs index 243a18d76d..ee510b36da 100644 --- a/frontend/rust-lib/flowy-ai/src/ai_manager.rs +++ b/frontend/rust-lib/flowy-ai/src/ai_manager.rs @@ -23,7 +23,7 @@ pub trait AIUserService: Send + Sync + 'static { fn device_id(&self) -> Result; fn workspace_id(&self) -> Result; fn sqlite_connection(&self, uid: i64) -> Result; - fn data_root_dir(&self) -> Result; + fn application_root_dir(&self) -> Result; } pub struct AIManager { @@ -91,10 +91,6 @@ impl AIManager { Ok(()) } - pub fn is_using_local_ai(&self) -> bool { - self.local_ai_controller.is_running() - } - pub async fn delete_chat(&self, chat_id: &str) -> Result<(), FlowyError> { if let Some((_, chat)) = self.chats.remove(chat_id) { chat.close(); diff --git a/frontend/rust-lib/flowy-ai/src/chat.rs b/frontend/rust-lib/flowy-ai/src/chat.rs index 90e7aae401..1e8849828a 100644 --- a/frontend/rust-lib/flowy-ai/src/chat.rs +++ b/frontend/rust-lib/flowy-ai/src/chat.rs @@ -104,7 +104,7 @@ impl Chat { &self.chat_id, message, message_type, - metadata, + &metadata, ) .await .map_err(|err| { @@ -112,6 +112,16 @@ impl Chat { FlowyError::server_error() })?; + if self.chat_service.is_local_ai_enabled() { + if let Err(err) = self + .chat_service + .index_message_metadata(&self.chat_id, &metadata) + .await + { + error!("Failed to index file: {}", err); + } + } + 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 3b7f46dcc6..b18a59504f 100644 --- a/frontend/rust-lib/flowy-ai/src/event_handler.rs +++ b/frontend/rust-lib/flowy-ai/src/event_handler.rs @@ -15,6 +15,7 @@ use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataRes use lib_infra::isolate_stream::IsolateSink; use std::sync::{Arc, Weak}; use tokio::sync::oneshot; +use tracing::trace; use validator::Validate; fn upgrade_ai_manager(ai_manager: AFPluginState>) -> FlowyResult> { @@ -29,7 +30,6 @@ pub(crate) async fn stream_chat_message_handler( data: AFPluginData, ai_manager: AFPluginState>, ) -> DataResult { - let ai_manager = upgrade_ai_manager(ai_manager)?; let data = data.into_inner(); data.validate()?; @@ -37,28 +37,21 @@ pub(crate) async fn stream_chat_message_handler( ChatMessageTypePB::System => ChatMessageType::System, ChatMessageTypePB::User => ChatMessageType::User, }; - let is_using_local_ai = ai_manager.is_using_local_ai(); - let metadata = data .metadata .into_iter() .map(|metadata| { - let (content_type, content_len) = if is_using_local_ai { - (ChatMetadataContentType::Unknown, 0) - } else { - match metadata.data_type { - ChatMessageMetaTypePB::Txt => (ChatMetadataContentType::Text, metadata.data.len()), - ChatMessageMetaTypePB::Markdown => { - (ChatMetadataContentType::Markdown, metadata.data.len()) - }, - ChatMessageMetaTypePB::PDF => (ChatMetadataContentType::PDF, 0), - ChatMessageMetaTypePB::UnknownMetaType => (ChatMetadataContentType::Unknown, 0), - } + let (content_type, content_len) = match metadata.data_type { + ChatMessageMetaTypePB::Txt => (ChatMetadataContentType::Text, metadata.data.len()), + ChatMessageMetaTypePB::Markdown => (ChatMetadataContentType::Markdown, metadata.data.len()), + ChatMessageMetaTypePB::PDF => (ChatMetadataContentType::PDF, 0), + ChatMessageMetaTypePB::UnknownMetaType => (ChatMetadataContentType::Unknown, 0), }; ChatMessageMetadata { data: ChatMetadataData { content: metadata.data, + url: None, content_type, size: content_len as i64, }, @@ -70,15 +63,23 @@ pub(crate) async fn stream_chat_message_handler( }) .collect::>(); - let question = ai_manager - .stream_chat_message( - &data.chat_id, - &data.message, - message_type, - data.text_stream_port, - metadata, - ) - .await?; + 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.text_stream_port, + metadata, + ) + .await; + let _ = tx.send(result); + }); + + let question = rx.await??; data_result_ok(question) } 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 9936ceba2f..a3ba55b9a5 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 @@ -6,15 +6,21 @@ use anyhow::Error; use appflowy_local_ai::chat_plugin::{AIPluginConfig, AppFlowyLocalAI}; use appflowy_plugin::manager::PluginManager; use appflowy_plugin::util::is_apple_silicon; -use flowy_ai_pub::cloud::{AppFlowyOfflineAI, ChatCloudService, LLMModel, LocalAIConfig}; +use flowy_ai_pub::cloud::{ + AppFlowyOfflineAI, ChatCloudService, ChatMessageMetadata, ChatMetadataContentType, LLMModel, + LocalAIConfig, +}; use flowy_error::{FlowyError, FlowyResult}; use flowy_sqlite::kv::KVStorePreferences; use futures::Sink; use lib_infra::async_trait::async_trait; +use std::collections::HashMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; +use serde_json::json; use std::ops::Deref; +use std::path::Path; use std::sync::Arc; use tokio::select; use tokio_stream::StreamExt; @@ -173,7 +179,7 @@ impl LocalAIController { } pub fn open_chat(&self, chat_id: &str) { - if !self.is_running() { + if !self.is_enabled() { return; } @@ -334,6 +340,63 @@ impl LocalAIController { Ok(enabled) } + pub async fn index_message_metadata( + &self, + chat_id: &str, + metadata_list: &[ChatMessageMetadata], + ) -> FlowyResult<()> { + for metadata in metadata_list { + 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)); + + 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); + } + } + }, + } + } + + 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/local_ai/local_llm_resource.rs b/frontend/rust-lib/flowy-ai/src/local_ai/local_llm_resource.rs index 3dbf19e967..2b51ef7a61 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 @@ -514,7 +514,7 @@ impl LocalAIResourceController { } pub(crate) fn resource_dir(&self) -> FlowyResult { - let user_data_dir = self.user_service.data_root_dir()?; + let user_data_dir = self.user_service.application_root_dir()?; Ok(user_data_dir.join("ai")) } } 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 76a961531c..b52ef92810 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 @@ -2,7 +2,7 @@ use crate::ai_manager::AIUserService; use crate::entities::{ChatStatePB, ModelTypePB}; use crate::local_ai::local_llm_chat::LocalAIController; use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY}; -use crate::persistence::select_single_message; +use crate::persistence::{select_single_message, ChatMessageTable}; use appflowy_plugin::error::PluginError; use flowy_ai_pub::cloud::{ @@ -16,8 +16,10 @@ use lib_infra::async_trait::async_trait; use lib_infra::future::FutureResult; use crate::local_ai::stream_util::LocalAIStreamAdaptor; +use serde_json::json; use std::path::Path; use std::sync::Arc; +use tracing::trace; pub struct AICloudServiceMiddleware { cloud_service: Arc, @@ -38,16 +40,30 @@ impl AICloudServiceMiddleware { } } - fn get_message_content(&self, message_id: i64) -> FlowyResult { + pub fn is_local_ai_enabled(&self) -> bool { + self.local_llm_controller.is_enabled() + } + + pub async fn index_message_metadata( + &self, + chat_id: &str, + metadata_list: &[ChatMessageMetadata], + ) -> Result<(), FlowyError> { + self + .local_llm_controller + .index_message_metadata(chat_id, metadata_list) + .await?; + Ok(()) + } + + fn get_message_record(&self, message_id: i64) -> FlowyResult { let uid = self.user_service.user_id()?; let conn = self.user_service.sqlite_connection(uid)?; - let content = select_single_message(conn, message_id)? - .map(|data| data.content) - .ok_or_else(|| { - FlowyError::record_not_found().with_context(format!("Message not found: {}", message_id)) - })?; + let row = select_single_message(conn, message_id)?.ok_or_else(|| { + FlowyError::record_not_found().with_context(format!("Message not found: {}", message_id)) + })?; - Ok(content) + Ok(row) } fn handle_plugin_error(&self, err: PluginError) { @@ -79,17 +95,18 @@ impl ChatCloudService for AICloudServiceMiddleware { self.cloud_service.create_chat(uid, workspace_id, chat_id) } - fn create_question( + async fn create_question( &self, workspace_id: &str, chat_id: &str, message: &str, message_type: ChatMessageType, - metadata: Vec, - ) -> FutureResult { + metadata: &[ChatMessageMetadata], + ) -> Result { self .cloud_service .create_question(workspace_id, chat_id, message, message_type, metadata) + .await } fn create_answer( @@ -109,13 +126,13 @@ impl ChatCloudService for AICloudServiceMiddleware { &self, workspace_id: &str, chat_id: &str, - message_id: i64, + question_id: i64, ) -> Result { if self.local_llm_controller.is_running() { - let content = self.get_message_content(message_id)?; + let row = self.get_message_record(question_id)?; match self .local_llm_controller - .stream_question(chat_id, &content) + .stream_question(chat_id, &row.content, json!([])) .await { Ok(stream) => Ok(LocalAIStreamAdaptor::new(stream).boxed()), @@ -127,7 +144,7 @@ impl ChatCloudService for AICloudServiceMiddleware { } else { self .cloud_service - .stream_answer(workspace_id, chat_id, message_id) + .stream_answer(workspace_id, chat_id, question_id) .await } } @@ -139,7 +156,7 @@ impl ChatCloudService for AICloudServiceMiddleware { question_message_id: i64, ) -> Result { if self.local_llm_controller.is_running() { - let content = self.get_message_content(question_message_id)?; + let content = self.get_message_record(question_message_id)?.content; match self .local_llm_controller .ask_question(chat_id, &content) @@ -189,7 +206,10 @@ impl ChatCloudService for AICloudServiceMiddleware { .local_llm_controller .get_related_question(chat_id) .await - .map_err(|err| FlowyError::local_ai().with_context(err))? + .map_err(|err| FlowyError::local_ai().with_context(err))?; + trace!("LocalAI related questions: {:?}", questions); + + let items = questions .into_iter() .map(|content| RelatedQuestion { content, @@ -197,10 +217,7 @@ impl ChatCloudService for AICloudServiceMiddleware { }) .collect::>(); - Ok(RepeatedRelatedQuestion { - message_id, - items: questions, - }) + Ok(RepeatedRelatedQuestion { message_id, items }) } else { self .cloud_service @@ -248,7 +265,7 @@ impl ChatCloudService for AICloudServiceMiddleware { if self.local_llm_controller.is_running() { self .local_llm_controller - .index_file(chat_id, file_path.to_path_buf()) + .index_file(chat_id, Some(file_path.to_path_buf()), None, None) .await .map_err(|err| FlowyError::local_ai().with_context(err))?; Ok(()) 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 cb6975a7a0..1cc7c5c490 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 @@ -52,7 +52,7 @@ impl AIUserService for ChatUserServiceImpl { self.upgrade_user()?.get_sqlite_connection(uid) } - fn data_root_dir(&self) -> Result { + fn application_root_dir(&self) -> Result { Ok(PathBuf::from( self.upgrade_user()?.get_application_root_dir(), )) 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 da2d9d41e6..26e17f2a5c 100644 --- a/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs +++ b/frontend/rust-lib/flowy-core/src/integrate/trait_impls.rs @@ -611,25 +611,22 @@ impl ChatCloudService for ServerProvider { }) } - fn create_question( + async fn create_question( &self, workspace_id: &str, chat_id: &str, message: &str, message_type: ChatMessageType, - metadata: Vec, - ) -> FutureResult { + metadata: &[ChatMessageMetadata], + ) -> Result { let workspace_id = workspace_id.to_string(); let chat_id = chat_id.to_string(); let message = message.to_string(); - let server = self.get_server(); - - FutureResult::new(async move { - server? - .chat_service() - .create_question(&workspace_id, &chat_id, &message, message_type, metadata) - .await - }) + self + .get_server()? + .chat_service() + .create_question(&workspace_id, &chat_id, &message, message_type, metadata) + .await } fn create_answer( 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 ca07dbc285..43fbc74cf0 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 @@ -52,14 +52,14 @@ where }) } - fn create_question( + async fn create_question( &self, workspace_id: &str, chat_id: &str, message: &str, message_type: ChatMessageType, - metadata: Vec, - ) -> FutureResult { + metadata: &[ChatMessageMetadata], + ) -> Result { let workspace_id = workspace_id.to_string(); let chat_id = chat_id.to_string(); let try_get_client = self.inner.try_get_client(); @@ -69,13 +69,11 @@ where metadata: Some(json!(metadata)), }; - FutureResult::new(async move { - let message = try_get_client? - .create_question(&workspace_id, &chat_id, params) - .await - .map_err(FlowyError::from)?; - Ok(message) - }) + let message = try_get_client? + .create_question(&workspace_id, &chat_id, params) + .await + .map_err(FlowyError::from)?; + Ok(message) } fn create_answer( diff --git a/frontend/rust-lib/flowy-server/src/default_impl.rs b/frontend/rust-lib/flowy-server/src/default_impl.rs index 22f6688ba9..ff409faeae 100644 --- a/frontend/rust-lib/flowy-server/src/default_impl.rs +++ b/frontend/rust-lib/flowy-server/src/default_impl.rs @@ -23,17 +23,15 @@ impl ChatCloudService for DefaultChatCloudServiceImpl { }) } - fn create_question( + async fn create_question( &self, _workspace_id: &str, _chat_id: &str, _message: &str, _message_type: ChatMessageType, - _metadata: Vec, - ) -> FutureResult { - FutureResult::new(async move { - Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) - }) + _metadata: &[ChatMessageMetadata], + ) -> Result { + Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) } fn create_answer(