feat: support mention a document as context on local ai (#5913)

* feat: support mention a document as context on local ai

* chore: rename

* chore: fix test

* chore: fix test
This commit is contained in:
Nathan.fooo 2024-08-09 21:55:20 +08:00 committed by GitHub
parent f84473c857
commit 758c304a74
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 721 additions and 672 deletions

View File

@ -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';

View File

@ -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<ChatEvent, ChatState> {
ChatBloc({
required ViewPB view,
@ -100,7 +92,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
_loadPrevMessage(beforeMessageId);
emit(
state.copyWith(
loadingPreviousStatus: const LoadingState.loading(),
loadingPreviousStatus: const ChatLoadingState.loading(),
),
);
},
@ -116,7 +108,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
emit(
state.copyWith(
messages: uniqueMessages,
loadingPreviousStatus: const LoadingState.finish(),
loadingPreviousStatus: const ChatLoadingState.finish(),
hasMorePrevMessage: hasMore,
),
);
@ -130,7 +122,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
emit(
state.copyWith(
messages: uniqueMessages,
initialLoadingStatus: const LoadingState.finish(),
initialLoadingStatus: const ChatLoadingState.finish(),
),
);
},
@ -200,6 +192,14 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
sendMessage: (String message, Map<String, dynamic>? 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<ChatEvent, ChatState> {
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<String, dynamic> toMap() {
return {
onetimeShotType: toString(),
};
}
}
OnetimeShotType? onetimeMessageTypeFromMeta(Map<String, dynamic>? 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<String> _controller = StreamController.broadcast();
late StreamSubscription<String> _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<ChatMessageRefSource> metadata)? _onMetadata;
int get nativePort => _port.sendPort.nativePort;
bool get hasStarted => _hasStarted;
String? get error => _error;
String get text => _text;
Future<void> 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<ChatMessageRefSource> 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<String, dynamic> json) =>
_$ChatMessageRefSourceFromJson(json);
final String id;
final String name;
final String source;
Map<String, dynamic> 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;
}

View File

@ -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<String, dynamic> json) =>
_$ChatMessageRefSourceFromJson(json);
final String id;
final String name;
final String source;
Map<String, dynamic> 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<Object?> 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<String, ChatFile>;
@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<String, dynamic> toMap() {
return {
onetimeShotType: toString(),
};
}
}
OnetimeShotType? onetimeMessageTypeFromMeta(Map<String, dynamic>? 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;
}

View File

@ -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<String, ChatFile>;
class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
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<Object?> 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);
}
}
}

View File

@ -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';

View File

@ -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<ChatFile> fileListFromMessageMetadata(
Map<String, dynamic>? map,
) {
@ -117,7 +115,7 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
name: view.name,
data: pb.text,
dataType: ChatMessageMetaTypePB.Txt,
source: "appflowy document",
source: "appflowy",
),
);
}, (err) {

View File

@ -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<String> _controller = StreamController.broadcast();
late StreamSubscription<String> _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<ChatMessageRefSource> metadata)? _onMetadata;
int get nativePort => _port.sendPort.nativePort;
bool get hasStarted => _hasStarted;
String? get error => _error;
String get text => _text;
Future<void> 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<ChatMessageRefSource> metadata)? onMetadata,
}) {
_onData = onData;
_onStart = onStart;
_onEnd = onEnd;
_onError = onError;
_onAIResponseLimit = onAIResponseLimit;
_onMetadata = onMetadata;
if (_onStart != null) {
_onStart!();
}
}
}

View File

@ -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';

View File

@ -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<ChatUserMessageEvent, ChatUserMessageState> {
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<ChatFile> files,
}) = _ChatUserMessageState;
factory ChatUserMessageState.initial(
TextMessage message,
Message message,
List<ChatFile> files,
) =>
ChatUserMessageState(message: message, files: files);

View File

@ -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<ChatBloc>()
.add(const ChatEvent.startLoadingPrevMessage());
}
},
emptyState: BlocBuilder<ChatBloc, ChatState>(
builder: (_, state) =>
state.initialLoadingStatus == const LoadingState.finish()
? Padding(
padding: AIChatUILayout.welcomePagePadding,
child: ChatWelcomePage(
userProfile: widget.userProfile,
onSelectedQuestion: (question) => blocContext
.read<ChatBloc>()
.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<ChatBloc>()
.add(ChatEvent.sendMessage(message: question)),
),
)
: const Center(
child: CircularProgressIndicator.adaptive(),
),
),
messageWidthRatio: AIChatUILayout.messageWidthRatio,
textMessageBuilder: (

View File

@ -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<ChatInput> {
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<ChatInput> {
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<ChatInput> {
}
Future<void> _handleOnTextChange(BuildContext context, String text) async {
if (widget.aiType != const AIType.appflowyAI()) {
return;
}
if (!_inputActionControl.onTextChanged(text)) {
return;
}

View File

@ -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),

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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<ChatMemberBloc>()
.add(ChatMemberEvent.getMemberInfo(message.author.id));
}
final metadata = message.metadata?[messageMetadataKey] as String?;
return BlocConsumer<ChatMemberBloc, ChatMemberState>(
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<ChatUserMessageBloc, ChatUserMessageState>(
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<ChatMemberBloc, ChatMemberState>(
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<ChatFile> files;
@override
Widget build(BuildContext context) {
final List<Widget> 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<ChatUserMessageHover> createState() => _ChatUserMessageHoverState();
}
class _ChatUserMessageHoverState extends State<ChatUserMessageHover> {
bool _isHover = false;
@override
void initState() {
super.initState();
_isHover = widget.autoShowHover ? false : true;
}
@override
Widget build(BuildContext context) {
final List<Widget> 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: () {},
),
);
}

View File

@ -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<ChatUserMessageBloc, ChatUserMessageState>(
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<ChatFile> files;
@override
Widget build(BuildContext context) {
final List<Widget> 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,
),
),
],
),
),
);
}
}

View File

@ -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<DownloadModelEvent, DownloadModelState> {
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;
}

View File

@ -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<LLMModelPB> models,
@Default(RunningStatePB.Connecting) RunningStatePB runningState,
}) = _LocalAIChatSettingState;

View File

@ -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] ?? {};

View File

@ -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",

View File

@ -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" }

View File

@ -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",

View File

@ -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" }

View File

@ -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",

View File

@ -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" }

View File

@ -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();

View File

@ -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<ChatMessageMetadata>,
) -> FutureResult<ChatMessage, FlowyError>;
metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError>;
fn create_answer(
&self,

View File

@ -23,7 +23,7 @@ pub trait AIUserService: Send + Sync + 'static {
fn device_id(&self) -> Result<String, FlowyError>;
fn workspace_id(&self) -> Result<String, FlowyError>;
fn sqlite_connection(&self, uid: i64) -> Result<DBConnection, FlowyError>;
fn data_root_dir(&self) -> Result<PathBuf, FlowyError>;
fn application_root_dir(&self) -> Result<PathBuf, FlowyError>;
}
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();

View File

@ -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,

View File

@ -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<Weak<AIManager>>) -> FlowyResult<Arc<AIManager>> {
@ -29,7 +30,6 @@ pub(crate) async fn stream_chat_message_handler(
data: AFPluginData<StreamChatPayloadPB>,
ai_manager: AFPluginState<Weak<AIManager>>,
) -> DataResult<ChatMessagePB, FlowyError> {
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::<Vec<_>>();
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::<Result<ChatMessagePB, FlowyError>>();
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)
}

View File

@ -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 {

View File

@ -514,7 +514,7 @@ impl LocalAIResourceController {
}
pub(crate) fn resource_dir(&self) -> FlowyResult<PathBuf> {
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"))
}
}

View File

@ -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<dyn ChatCloudService>,
@ -38,16 +40,30 @@ impl AICloudServiceMiddleware {
}
}
fn get_message_content(&self, message_id: i64) -> FlowyResult<String> {
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<ChatMessageTable> {
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<ChatMessageMetadata>,
) -> FutureResult<ChatMessage, FlowyError> {
metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError> {
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<StreamAnswer, FlowyError> {
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<ChatMessage, FlowyError> {
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::<Vec<_>>();
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(())

View File

@ -52,7 +52,7 @@ impl AIUserService for ChatUserServiceImpl {
self.upgrade_user()?.get_sqlite_connection(uid)
}
fn data_root_dir(&self) -> Result<PathBuf, FlowyError> {
fn application_root_dir(&self) -> Result<PathBuf, FlowyError> {
Ok(PathBuf::from(
self.upgrade_user()?.get_application_root_dir(),
))

View File

@ -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<ChatMessageMetadata>,
) -> FutureResult<ChatMessage, FlowyError> {
metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError> {
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(

View File

@ -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<ChatMessageMetadata>,
) -> FutureResult<ChatMessage, FlowyError> {
metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError> {
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(

View File

@ -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<ChatMessageMetadata>,
) -> FutureResult<ChatMessage, FlowyError> {
FutureResult::new(async move {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
})
_metadata: &[ChatMessageMetadata],
) -> Result<ChatMessage, FlowyError> {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
}
fn create_answer(