mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
f84473c857
commit
758c304a74
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -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) {
|
||||
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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);
|
||||
|
@ -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: (
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -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: () {},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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] ?? {};
|
||||
|
34
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
34
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
32
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
32
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
||||
|
50
frontend/rust-lib/Cargo.lock
generated
50
frontend/rust-lib/Cargo.lock
generated
@ -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",
|
||||
|
@ -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" }
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"))
|
||||
}
|
||||
}
|
||||
|
@ -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(())
|
||||
|
@ -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(),
|
||||
))
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user