mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 11:15:53 +03:00
chore: chat UI poblish (#5895)
* chore: update ui * chore: update send state * chore: workspace owner prompt * chore: show other user * chore: fix ui
This commit is contained in:
parent
98b7882d43
commit
e28a251e72
@ -26,6 +26,8 @@ part 'chat_bloc.g.dart';
|
||||
part 'chat_bloc.freezed.dart';
|
||||
|
||||
const sendMessageErrorKey = "sendMessageError";
|
||||
const systemUserId = "system";
|
||||
const aiResponseUserId = "0";
|
||||
|
||||
class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
ChatBloc({
|
||||
@ -87,6 +89,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
},
|
||||
);
|
||||
},
|
||||
// Loading messages
|
||||
startLoadingPrevMessage: () async {
|
||||
Int64? beforeMessageId;
|
||||
final oldestMessage = _getOlderstMessage();
|
||||
@ -130,21 +133,58 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
// streaming message
|
||||
streaming: (Message message) {
|
||||
final allMessages = _perminentMessages();
|
||||
allMessages.insert(0, message);
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
streamingStatus: const LoadingState.loading(),
|
||||
streamingState: const StreamingState.streaming(),
|
||||
canSendMessage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
didFinishStreaming: () {
|
||||
finishStreaming: () {
|
||||
emit(
|
||||
state.copyWith(streamingStatus: const LoadingState.finish()),
|
||||
state.copyWith(
|
||||
streamingState: const StreamingState.done(),
|
||||
canSendMessage:
|
||||
state.sendingState == const SendMessageState.done(),
|
||||
),
|
||||
);
|
||||
},
|
||||
didUpdateAnswerStream: (AnswerStream stream) {
|
||||
emit(state.copyWith(answerStream: stream));
|
||||
},
|
||||
stopStream: () async {
|
||||
if (state.answerStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = StopStreamPB(chatId: chatId);
|
||||
await AIEventStopStream(payload).send();
|
||||
final allMessages = _perminentMessages();
|
||||
if (state.streamingState != const StreamingState.done()) {
|
||||
// If the streaming is not started, remove the message from the list
|
||||
if (!state.answerStream!.hasStarted) {
|
||||
allMessages.removeWhere(
|
||||
(element) => element.id == lastStreamMessageId,
|
||||
);
|
||||
lastStreamMessageId = "";
|
||||
}
|
||||
|
||||
// when stop stream, we will set the answer stream to null. Which means the streaming
|
||||
// is finished or canceled.
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
answerStream: null,
|
||||
streamingState: const StreamingState.done(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
receveMessage: (Message message) {
|
||||
final allMessages = _perminentMessages();
|
||||
// remove message with the same id
|
||||
@ -164,15 +204,32 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
lastSentMessage: null,
|
||||
messages: allMessages,
|
||||
relatedQuestions: [],
|
||||
sendingState: const SendMessageState.sending(),
|
||||
canSendMessage: false,
|
||||
),
|
||||
);
|
||||
},
|
||||
finishSending: (ChatMessagePB message) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastSentMessage: message,
|
||||
sendingState: const SendMessageState.done(),
|
||||
canSendMessage:
|
||||
state.streamingState == const StreamingState.done(),
|
||||
),
|
||||
);
|
||||
},
|
||||
// related question
|
||||
didReceiveRelatedQuestion: (List<RelatedQuestionPB> questions) {
|
||||
if (questions.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final allMessages = _perminentMessages();
|
||||
final message = CustomMessage(
|
||||
metadata: OnetimeShotType.relatedQuestion.toMap(),
|
||||
author: const User(id: "system"),
|
||||
id: 'system',
|
||||
author: const User(id: systemUserId),
|
||||
id: systemUserId,
|
||||
);
|
||||
allMessages.insert(0, message);
|
||||
emit(
|
||||
@ -189,44 +246,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
didSentUserMessage: (ChatMessagePB message) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
lastSentMessage: message,
|
||||
),
|
||||
);
|
||||
},
|
||||
didUpdateAnswerStream: (AnswerStream stream) {
|
||||
emit(state.copyWith(answerStream: stream));
|
||||
},
|
||||
stopStream: () async {
|
||||
if (state.answerStream == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = StopStreamPB(chatId: chatId);
|
||||
await AIEventStopStream(payload).send();
|
||||
final allMessages = _perminentMessages();
|
||||
if (state.streamingStatus != const LoadingState.finish()) {
|
||||
// If the streaming is not started, remove the message from the list
|
||||
if (!state.answerStream!.hasStarted) {
|
||||
allMessages.removeWhere(
|
||||
(element) => element.id == lastStreamMessageId,
|
||||
);
|
||||
lastStreamMessageId = "";
|
||||
}
|
||||
|
||||
// when stop stream, we will set the answer stream to null. Which means the streaming
|
||||
// is finished or canceled.
|
||||
emit(
|
||||
state.copyWith(
|
||||
messages: allMessages,
|
||||
answerStream: null,
|
||||
streamingStatus: const LoadingState.finish(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -250,7 +269,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
chatErrorMessageCallback: (err) {
|
||||
if (!isClosed) {
|
||||
Log.error("chat error: ${err.errorMessage}");
|
||||
add(const ChatEvent.didFinishStreaming());
|
||||
add(const ChatEvent.finishStreaming());
|
||||
}
|
||||
},
|
||||
latestMessageCallback: (list) {
|
||||
@ -267,7 +286,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
},
|
||||
finishStreamingCallback: () {
|
||||
if (!isClosed) {
|
||||
add(const ChatEvent.didFinishStreaming());
|
||||
add(const ChatEvent.finishStreaming());
|
||||
// The answer strema will bet set to null after the streaming is finished or canceled.
|
||||
// so if the answer stream is null, we will not get related question.
|
||||
if (state.lastSentMessage != null && state.answerStream != null) {
|
||||
@ -353,7 +372,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
result.fold(
|
||||
(ChatMessagePB question) {
|
||||
if (!isClosed) {
|
||||
add(ChatEvent.didSentUserMessage(question));
|
||||
add(ChatEvent.finishSending(question));
|
||||
|
||||
final questionMessageId = question.messageId;
|
||||
final message = _createTextMessage(question);
|
||||
@ -374,8 +393,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
|
||||
final error = CustomMessage(
|
||||
metadata: metadata,
|
||||
author: const User(id: "system"),
|
||||
id: 'system',
|
||||
author: const User(id: systemUserId),
|
||||
id: systemUserId,
|
||||
);
|
||||
|
||||
add(ChatEvent.receveMessage(error));
|
||||
@ -390,7 +409,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
lastStreamMessageId = streamMessageId;
|
||||
|
||||
return TextMessage(
|
||||
author: User(id: nanoid()),
|
||||
author: User(id: "streamId:${nanoid()}"),
|
||||
metadata: {
|
||||
"$AnswerStream": stream,
|
||||
"question": questionMessageId,
|
||||
@ -425,10 +444,21 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
|
||||
@freezed
|
||||
class ChatEvent with _$ChatEvent {
|
||||
const factory ChatEvent.initialLoad() = _InitialLoadMessage;
|
||||
|
||||
// send message
|
||||
const factory ChatEvent.sendMessage({
|
||||
required String message,
|
||||
Map<String, dynamic>? metadata,
|
||||
}) = _SendMessage;
|
||||
const factory ChatEvent.finishSending(ChatMessagePB message) =
|
||||
_FinishSendMessage;
|
||||
|
||||
// receive message
|
||||
const factory ChatEvent.streaming(Message message) = _StreamingMessage;
|
||||
const factory ChatEvent.receveMessage(Message message) = _ReceiveMessage;
|
||||
const factory ChatEvent.finishStreaming() = _FinishStreamingMessage;
|
||||
|
||||
// loading messages
|
||||
const factory ChatEvent.startLoadingPrevMessage() = _StartLoadPrevMessage;
|
||||
const factory ChatEvent.didLoadPreviousMessages(
|
||||
List<Message> messages,
|
||||
@ -436,16 +466,13 @@ class ChatEvent with _$ChatEvent {
|
||||
) = _DidLoadPreviousMessages;
|
||||
const factory ChatEvent.didLoadLatestMessages(List<Message> messages) =
|
||||
_DidLoadMessages;
|
||||
const factory ChatEvent.streaming(Message message) = _StreamingMessage;
|
||||
const factory ChatEvent.receveMessage(Message message) = _ReceiveMessage;
|
||||
|
||||
const factory ChatEvent.didFinishStreaming() = _FinishStreamingMessage;
|
||||
// related questions
|
||||
const factory ChatEvent.didReceiveRelatedQuestion(
|
||||
List<RelatedQuestionPB> questions,
|
||||
) = _DidReceiveRelatedQueston;
|
||||
const factory ChatEvent.clearReleatedQuestion() = _ClearRelatedQuestion;
|
||||
const factory ChatEvent.didSentUserMessage(ChatMessagePB message) =
|
||||
_DidSendUserMessage;
|
||||
|
||||
const factory ChatEvent.didUpdateAnswerStream(
|
||||
AnswerStream stream,
|
||||
) = _DidUpdateAnswerStream;
|
||||
@ -466,7 +493,8 @@ class ChatState with _$ChatState {
|
||||
required LoadingState 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 LoadingState streamingStatus,
|
||||
required StreamingState streamingState,
|
||||
required SendMessageState sendingState,
|
||||
// Indicate whether there are more previous messages to load.
|
||||
required bool hasMorePrevMessage,
|
||||
// The related questions that are received after the user message is sent.
|
||||
@ -474,6 +502,7 @@ class ChatState with _$ChatState {
|
||||
// The last user message that is sent to the server.
|
||||
ChatMessagePB? lastSentMessage,
|
||||
AnswerStream? answerStream,
|
||||
@Default(true) bool canSendMessage,
|
||||
}) = _ChatState;
|
||||
|
||||
factory ChatState.initial(ViewPB view, UserProfilePB userProfile) =>
|
||||
@ -483,12 +512,19 @@ class ChatState with _$ChatState {
|
||||
userProfile: userProfile,
|
||||
initialLoadingStatus: const LoadingState.finish(),
|
||||
loadingPreviousStatus: const LoadingState.finish(),
|
||||
streamingStatus: const LoadingState.finish(),
|
||||
streamingState: const StreamingState.done(),
|
||||
sendingState: const SendMessageState.done(),
|
||||
hasMorePrevMessage: true,
|
||||
relatedQuestions: [],
|
||||
);
|
||||
}
|
||||
|
||||
bool isOtherUserMessage(Message message) {
|
||||
return message.author.id != aiResponseUserId &&
|
||||
message.author.id != systemUserId &&
|
||||
!message.author.id.startsWith("streamId:");
|
||||
}
|
||||
|
||||
@freezed
|
||||
class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.loading() = _Loading;
|
||||
@ -497,6 +533,7 @@ class LoadingState with _$LoadingState {
|
||||
|
||||
enum OnetimeShotType {
|
||||
unknown,
|
||||
sendingMessage,
|
||||
relatedQuestion,
|
||||
invalidSendMesssage,
|
||||
}
|
||||
@ -638,7 +675,9 @@ List<ChatMessageMetadata> chatMessageMetadataFromString(String? s) {
|
||||
}
|
||||
|
||||
if (metadataJson is Map<String, dynamic>) {
|
||||
metadata.add(ChatMessageMetadata.fromJson(metadataJson));
|
||||
if (metadataJson.isNotEmpty) {
|
||||
metadata.add(ChatMessageMetadata.fromJson(metadataJson));
|
||||
}
|
||||
} else if (metadataJson is List) {
|
||||
metadata.addAll(
|
||||
metadataJson.map(
|
||||
@ -672,3 +711,15 @@ class ChatMessageMetadata {
|
||||
|
||||
Map<String, dynamic> toJson() => _$ChatMessageMetadataToJson(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;
|
||||
}
|
||||
|
@ -108,6 +108,14 @@ class ChatInputActionBloc
|
||||
),
|
||||
);
|
||||
},
|
||||
clear: () {
|
||||
emit(
|
||||
state.copyWith(
|
||||
selectedPages: [],
|
||||
filter: "",
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -171,6 +179,7 @@ class ChatInputActionEvent with _$ChatInputActionEvent {
|
||||
const factory ChatInputActionEvent.addPage(ChatInputActionPage page) =
|
||||
_AddPage;
|
||||
const factory ChatInputActionEvent.removePage(String text) = _RemovePage;
|
||||
const factory ChatInputActionEvent.clear() = _Clear;
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -35,10 +35,18 @@ class ChatInputActionControl extends ChatActionHandler {
|
||||
List<String> get tags =>
|
||||
_commandBloc.state.selectedPages.map((e) => e.title).toList();
|
||||
|
||||
ChatInputMetadata get metaData => _commandBloc.state.selectedPages.fold(
|
||||
<String, ChatInputActionPage>{},
|
||||
(map, page) => map..putIfAbsent(page.pageId, () => page),
|
||||
);
|
||||
ChatInputMetadata consumeMetaData() {
|
||||
final metadata = _commandBloc.state.selectedPages.fold(
|
||||
<String, ChatInputActionPage>{},
|
||||
(map, page) => map..putIfAbsent(page.pageId, () => page),
|
||||
);
|
||||
|
||||
if (metadata.isNotEmpty) {
|
||||
_commandBloc.add(const ChatInputActionEvent.clear());
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
void handleKeyEvent(KeyEvent event) {
|
||||
// ignore: deprecated_member_use
|
||||
|
@ -0,0 +1,80 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'chat_member_bloc.freezed.dart';
|
||||
|
||||
class ChatMemberBloc extends Bloc<ChatMemberEvent, ChatMemberState> {
|
||||
ChatMemberBloc() : super(const ChatMemberState()) {
|
||||
on<ChatMemberEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {},
|
||||
receiveMemberInfo: (String id, WorkspaceMemberPB memberInfo) {
|
||||
final members = Map<String, ChatMember>.from(state.members);
|
||||
members[id] = ChatMember(info: memberInfo);
|
||||
emit(state.copyWith(members: members));
|
||||
},
|
||||
getMemberInfo: (String userId) {
|
||||
if (state.members.containsKey(userId)) {
|
||||
// Member info already exists. Debouncing refresh member info from backend would be better.
|
||||
return;
|
||||
}
|
||||
|
||||
final payload = WorkspaceMemberIdPB(
|
||||
uid: Int64.parseInt(userId),
|
||||
);
|
||||
UserEventGetMemberInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
result.fold((member) {
|
||||
add(
|
||||
ChatMemberEvent.receiveMemberInfo(
|
||||
userId,
|
||||
member,
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
Log.error("Error getting member info: $err");
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatMemberEvent with _$ChatMemberEvent {
|
||||
const factory ChatMemberEvent.initial() = Initial;
|
||||
const factory ChatMemberEvent.getMemberInfo(
|
||||
String userId,
|
||||
) = _GetMemberInfo;
|
||||
const factory ChatMemberEvent.receiveMemberInfo(
|
||||
String id,
|
||||
WorkspaceMemberPB memberInfo,
|
||||
) = _ReceiveMemberInfo;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatMemberState with _$ChatMemberState {
|
||||
const factory ChatMemberState({
|
||||
@Default({}) Map<String, ChatMember> members,
|
||||
}) = _ChatMemberState;
|
||||
}
|
||||
|
||||
class ChatMember extends Equatable {
|
||||
ChatMember({
|
||||
required this.info,
|
||||
});
|
||||
final DateTime _date = DateTime.now();
|
||||
final WorkspaceMemberPB info;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [_date, info];
|
||||
}
|
@ -7,7 +7,8 @@ import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
|
||||
Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
|
||||
Map<String, dynamic>? map,) async {
|
||||
Map<String, dynamic>? map,
|
||||
) async {
|
||||
final List<ChatMessageMetaPB> metadata = [];
|
||||
if (map != null) {
|
||||
for (final entry in map.entries) {
|
||||
@ -18,11 +19,14 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
|
||||
final payload = OpenDocumentPayloadPB(documentId: view.id);
|
||||
final result = await DocumentEventGetDocumentText(payload).send();
|
||||
result.fold((pb) {
|
||||
metadata.add(ChatMessageMetaPB(
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
text: pb.text,
|
||||
),);
|
||||
metadata.add(
|
||||
ChatMessageMetaPB(
|
||||
id: view.id,
|
||||
name: view.name,
|
||||
text: pb.text,
|
||||
source: "appflowy document",
|
||||
),
|
||||
);
|
||||
}, (err) {
|
||||
Log.error('Failed to get document text: $err');
|
||||
});
|
||||
|
@ -1,7 +1,4 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_member_bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@ -12,25 +9,14 @@ class ChatUserMessageBloc
|
||||
extends Bloc<ChatUserMessageEvent, ChatUserMessageState> {
|
||||
ChatUserMessageBloc({
|
||||
required Message message,
|
||||
}) : super(ChatUserMessageState.initial(message)) {
|
||||
required ChatMember? member,
|
||||
}) : super(ChatUserMessageState.initial(message, member)) {
|
||||
on<ChatUserMessageEvent>(
|
||||
(event, emit) async {
|
||||
event.when(
|
||||
initial: () {
|
||||
final payload =
|
||||
WorkspaceMemberIdPB(uid: Int64.parseInt(message.author.id));
|
||||
UserEventGetMemberInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
result.fold((member) {
|
||||
add(ChatUserMessageEvent.didReceiveMemberInfo(member));
|
||||
}, (err) {
|
||||
Log.error("Error getting member info: $err");
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
didReceiveMemberInfo: (WorkspaceMemberPB memberInfo) {
|
||||
emit(state.copyWith(member: memberInfo));
|
||||
initial: () {},
|
||||
refreshMember: (ChatMember member) {
|
||||
emit(state.copyWith(member: member));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -41,18 +27,20 @@ class ChatUserMessageBloc
|
||||
@freezed
|
||||
class ChatUserMessageEvent with _$ChatUserMessageEvent {
|
||||
const factory ChatUserMessageEvent.initial() = Initial;
|
||||
const factory ChatUserMessageEvent.didReceiveMemberInfo(
|
||||
WorkspaceMemberPB memberInfo,
|
||||
) = _MemberInfo;
|
||||
const factory ChatUserMessageEvent.refreshMember(ChatMember member) =
|
||||
_MemberInfo;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class ChatUserMessageState with _$ChatUserMessageState {
|
||||
const factory ChatUserMessageState({
|
||||
required Message message,
|
||||
WorkspaceMemberPB? member,
|
||||
ChatMember? member,
|
||||
}) = _ChatUserMessageState;
|
||||
|
||||
factory ChatUserMessageState.initial(Message message) =>
|
||||
ChatUserMessageState(message: message);
|
||||
factory ChatUserMessageState.initial(
|
||||
Message message,
|
||||
ChatMember? member,
|
||||
) =>
|
||||
ChatUserMessageState(message: message, member: member);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import 'dart:math';
|
||||
|
||||
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/presentation/other_user_message_bubble.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:desktop_drop/desktop_drop.dart';
|
||||
@ -27,6 +28,7 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart' show Chat;
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'application/chat_member_bloc.dart';
|
||||
import 'application/chat_side_pannel_bloc.dart';
|
||||
import 'presentation/chat_input/chat_input.dart';
|
||||
import 'presentation/chat_popmenu.dart';
|
||||
@ -101,6 +103,7 @@ class AIChatPage extends StatelessWidget {
|
||||
ChatInputStateBloc()..add(const ChatInputStateEvent.started()),
|
||||
),
|
||||
BlocProvider(create: (_) => ChatSidePannelBloc(chatId: view.id)),
|
||||
BlocProvider(create: (_) => ChatMemberBloc()),
|
||||
],
|
||||
child: BlocListener<ChatFileBloc, ChatFileState>(
|
||||
listenWhen: (previous, current) =>
|
||||
@ -298,6 +301,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
Widget buildChatWidget() {
|
||||
return BlocBuilder<ChatBloc, ChatState>(
|
||||
builder: (blocContext, state) => Chat(
|
||||
key: ValueKey(widget.view.id),
|
||||
messages: state.messages,
|
||||
onSendPressed: (_) {
|
||||
// We use custom bottom widget for chat input, so
|
||||
@ -335,7 +339,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
required messageWidth,
|
||||
required showName,
|
||||
}) =>
|
||||
_buildAITextMessage(blocContext, textMessage),
|
||||
_buildTextMessage(blocContext, textMessage),
|
||||
bubbleBuilder: (
|
||||
child, {
|
||||
required message,
|
||||
@ -346,17 +350,21 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
} else if (isOtherUserMessage(message)) {
|
||||
return OtherUserMessageBubble(
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
} else {
|
||||
return _buildAIBubble(message, blocContext, state, child);
|
||||
}
|
||||
|
||||
return _buildAIBubble(message, blocContext, state, child);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAITextMessage(BuildContext context, TextMessage message) {
|
||||
final isAuthor = message.author.id == _user.id;
|
||||
if (isAuthor) {
|
||||
Widget _buildTextMessage(BuildContext context, TextMessage message) {
|
||||
if (message.author.id == _user.id) {
|
||||
return ChatTextMessageWidget(
|
||||
user: message.author,
|
||||
messageUserId: message.id,
|
||||
@ -497,9 +505,9 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
BlocSelector<ChatBloc, ChatState, LoadingState>(
|
||||
selector: (state) => state.streamingStatus,
|
||||
builder: (context, state) {
|
||||
BlocSelector<ChatBloc, ChatState, bool>(
|
||||
selector: (state) => state.canSendMessage,
|
||||
builder: (context, canSendMessage) {
|
||||
return ChatInput(
|
||||
aiType: aiType,
|
||||
chatId: widget.view.id,
|
||||
@ -511,7 +519,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
|
||||
),
|
||||
);
|
||||
},
|
||||
isStreaming: state != const LoadingState.finish(),
|
||||
isStreaming: !canSendMessage,
|
||||
onStopStreaming: () {
|
||||
context
|
||||
.read<ChatBloc>()
|
||||
|
@ -55,11 +55,13 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
required this.name,
|
||||
required this.size,
|
||||
this.isHovering = false,
|
||||
this.defaultName,
|
||||
});
|
||||
|
||||
final String iconUrl;
|
||||
final String name;
|
||||
final double size;
|
||||
final String? defaultName;
|
||||
|
||||
// If true, a border will be applied on top of the avatar
|
||||
final bool isHovering;
|
||||
@ -76,7 +78,8 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildEmptyAvatar(BuildContext context) {
|
||||
final String nameOrDefault = _userName(name);
|
||||
final String nameOrDefault = _userName(name, defaultName);
|
||||
|
||||
final Color color = ColorGenerator(name).toColor();
|
||||
const initialsCount = 2;
|
||||
|
||||
@ -170,8 +173,8 @@ class ChatUserAvatar extends StatelessWidget {
|
||||
/// Return the user name, if the user name is empty,
|
||||
/// return the default user name.
|
||||
///
|
||||
String _userName(String name) =>
|
||||
name.isEmpty ? LocaleKeys.defaultUsername.tr() : name;
|
||||
String _userName(String name, String? defaultName) =>
|
||||
name.isEmpty ? (defaultName ?? LocaleKeys.defaultUsername.tr()) : name;
|
||||
|
||||
/// Used to darken the generated color for the hover border effect.
|
||||
/// The color is darkened by 15% - Hence the 0.15 value.
|
||||
|
@ -144,7 +144,7 @@ class _ChatInputState extends State<ChatInput> {
|
||||
if (trimmedText != '') {
|
||||
final partialText = types.PartialText(
|
||||
text: trimmedText,
|
||||
metadata: _inputActionControl.metaData,
|
||||
metadata: _inputActionControl.consumeMetaData(),
|
||||
);
|
||||
widget.onSendPressed(partialText);
|
||||
_textController.clear();
|
||||
|
@ -44,9 +44,12 @@ class AIMessageMetadata extends StatelessWidget {
|
||||
),
|
||||
useIntrinsicWidth: true,
|
||||
radius: BorderRadius.circular(6),
|
||||
text: FlowyText(
|
||||
m.source,
|
||||
fontSize: 14,
|
||||
text: Opacity(
|
||||
opacity: 0.5,
|
||||
child: FlowyText(
|
||||
m.name,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
onSelectedMetadata(m);
|
||||
|
@ -0,0 +1,212 @@
|
||||
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_member_bloc.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';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/markdown_to_document.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.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:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_chat_types/flutter_chat_types.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
const _leftPadding = 16.0;
|
||||
|
||||
class OtherUserMessageBubble extends StatelessWidget {
|
||||
const OtherUserMessageBubble({
|
||||
super.key,
|
||||
required this.message,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Message message;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const padding = EdgeInsets.symmetric(horizontal: _leftPadding);
|
||||
final childWithPadding = Padding(padding: padding, child: child);
|
||||
final widget = isMobile
|
||||
? _wrapPopMenu(childWithPadding)
|
||||
: _wrapHover(childWithPadding);
|
||||
|
||||
if (context.read<ChatMemberBloc>().state.members[message.author.id] ==
|
||||
null) {
|
||||
context
|
||||
.read<ChatMemberBloc>()
|
||||
.add(ChatMemberEvent.getMemberInfo(message.author.id));
|
||||
}
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
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 ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
size: 36,
|
||||
defaultName: "",
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(child: widget),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
OtherUserMessageHover _wrapHover(Padding child) {
|
||||
return OtherUserMessageHover(
|
||||
message: message,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
ChatPopupMenu _wrapPopMenu(Padding childWithPadding) {
|
||||
return ChatPopupMenu(
|
||||
onAction: (action) {
|
||||
if (action == ChatMessageAction.copy && message is TextMessage) {
|
||||
Clipboard.setData(ClipboardData(text: (message as TextMessage).text));
|
||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||
}
|
||||
},
|
||||
builder: (context) => childWithPadding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OtherUserMessageHover extends StatefulWidget {
|
||||
const OtherUserMessageHover({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final Message message;
|
||||
final bool autoShowHover = true;
|
||||
|
||||
@override
|
||||
State<OtherUserMessageHover> createState() => _OtherUserMessageHoverState();
|
||||
}
|
||||
|
||||
class _OtherUserMessageHoverState extends State<OtherUserMessageHover> {
|
||||
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) {
|
||||
children.addAll(_buildOnHoverItems());
|
||||
}
|
||||
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildOnHoverItems() {
|
||||
final List<Widget> children = [];
|
||||
if (widget.message is TextMessage) {
|
||||
children.add(
|
||||
CopyButton(
|
||||
textMessage: widget.message as TextMessage,
|
||||
).positioned(left: _leftPadding, bottom: 0),
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
class CopyButton extends StatelessWidget {
|
||||
const CopyButton({
|
||||
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: () async {
|
||||
final document = customMarkdownToDocument(textMessage.text);
|
||||
await getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: textMessage.text,
|
||||
inAppJson: jsonEncode(document.toJson()),
|
||||
),
|
||||
);
|
||||
if (context.mounted) {
|
||||
showToastNotification(
|
||||
context,
|
||||
message: LocaleKeys.grid_url_copiedNotification.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_user_message_bloc.dart';
|
||||
import 'package:appflowy/plugins/ai_chat/application/chat_member_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';
|
||||
@ -27,49 +27,53 @@ class ChatUserMessageBubble extends StatelessWidget {
|
||||
const borderRadius = BorderRadius.all(Radius.circular(6));
|
||||
final backgroundColor =
|
||||
Theme.of(context).colorScheme.surfaceContainerHighest;
|
||||
if (context.read<ChatMemberBloc>().state.members[message.author.id] ==
|
||||
null) {
|
||||
context
|
||||
.read<ChatMemberBloc>()
|
||||
.add(ChatMemberEvent.getMemberInfo(message.author.id));
|
||||
}
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => ChatUserMessageBloc(message: message)
|
||||
..add(const ChatUserMessageEvent.initial()),
|
||||
child: BlocBuilder<ChatUserMessageBloc, ChatUserMessageState>(
|
||||
builder: (context, state) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// _wrapHover(
|
||||
Flexible(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: borderRadius,
|
||||
color: backgroundColor,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: child,
|
||||
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,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
// ),
|
||||
BlocBuilder<ChatUserMessageBloc, ChatUserMessageState>(
|
||||
builder: (context, state) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ChatUserAvatar(
|
||||
iconUrl: state.member?.avatarUrl ?? "",
|
||||
name: state.member?.name ?? "",
|
||||
size: 36,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
// ),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ChatUserAvatar(
|
||||
iconUrl: member?.info.avatarUrl ?? "",
|
||||
name: member?.info.name ?? "",
|
||||
size: 36,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@ -10,12 +11,12 @@ part 'local_ai_on_boarding_bloc.freezed.dart';
|
||||
|
||||
class LocalAIOnBoardingBloc
|
||||
extends Bloc<LocalAIOnBoardingEvent, LocalAIOnBoardingState> {
|
||||
LocalAIOnBoardingBloc(this.workspaceId)
|
||||
LocalAIOnBoardingBloc(this.userProfile)
|
||||
: super(const LocalAIOnBoardingState()) {
|
||||
_dispatch();
|
||||
}
|
||||
|
||||
final String workspaceId;
|
||||
final UserProfilePB userProfile;
|
||||
|
||||
void _dispatch() {
|
||||
on<LocalAIOnBoardingEvent>((event, emit) {
|
||||
@ -44,7 +45,7 @@ class LocalAIOnBoardingBloc
|
||||
}
|
||||
|
||||
void _loadSubscriptionPlans() {
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = workspaceId;
|
||||
final payload = UserWorkspaceIdPB()..workspaceId = userProfile.workspaceId;
|
||||
UserEventGetWorkspaceSubscriptionInfo(payload).send().then((result) {
|
||||
if (!isClosed) {
|
||||
add(LocalAIOnBoardingEvent.didGetSubscriptionPlans(result));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -10,14 +11,31 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'settings_ai_bloc.freezed.dart';
|
||||
|
||||
class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
SettingsAIBloc(this.userProfile)
|
||||
SettingsAIBloc(this.userProfile, WorkspaceMemberPB? member)
|
||||
: _userListener = UserListener(userProfile: userProfile),
|
||||
super(SettingsAIState(userProfile: userProfile)) {
|
||||
_userService = UserBackendService(userId: userProfile.id),
|
||||
super(SettingsAIState(userProfile: userProfile, member: member)) {
|
||||
_dispatch();
|
||||
|
||||
if (member == null) {
|
||||
_userService.getWorkspaceMember().then((result) {
|
||||
result.fold(
|
||||
(member) {
|
||||
if (!isClosed) {
|
||||
add(SettingsAIEvent.refreshMember(member));
|
||||
}
|
||||
},
|
||||
(err) {
|
||||
Log.error(err);
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final UserListener _userListener;
|
||||
final UserProfilePB userProfile;
|
||||
final UserBackendService _userService;
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
@ -62,6 +80,9 @@ class SettingsAIBloc extends Bloc<SettingsAIEvent, SettingsAIState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
refreshMember: (member) {
|
||||
emit(state.copyWith(member: member));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -112,6 +133,7 @@ class SettingsAIEvent with _$SettingsAIEvent {
|
||||
) = _DidLoadWorkspaceSetting;
|
||||
|
||||
const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch;
|
||||
const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) = _RefreshMember;
|
||||
|
||||
const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel;
|
||||
|
||||
@ -125,6 +147,7 @@ class SettingsAIState with _$SettingsAIState {
|
||||
const factory SettingsAIState({
|
||||
required UserProfilePB userProfile,
|
||||
UseAISettingPB? aiSettings,
|
||||
WorkspaceMemberPB? member,
|
||||
@Default(true) bool enableSearchIndexing,
|
||||
}) = _SettingsAIState;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/shared/feature_flags.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||
@ -37,15 +38,20 @@ class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget {
|
||||
}
|
||||
|
||||
class SettingsAIView extends StatelessWidget {
|
||||
const SettingsAIView({super.key, required this.userProfile});
|
||||
const SettingsAIView({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
});
|
||||
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB? member;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsAIBloc>(
|
||||
create: (_) =>
|
||||
SettingsAIBloc(userProfile)..add(const SettingsAIEvent.started()),
|
||||
create: (_) => SettingsAIBloc(userProfile, member)
|
||||
..add(const SettingsAIEvent.started()),
|
||||
child: BlocBuilder<SettingsAIBloc, SettingsAIState>(
|
||||
builder: (context, state) {
|
||||
final children = <Widget>[
|
||||
@ -53,11 +59,15 @@ class SettingsAIView extends StatelessWidget {
|
||||
];
|
||||
|
||||
children.add(const _AISearchToggle(value: false));
|
||||
children.add(
|
||||
_LocalAIOnBoarding(
|
||||
workspaceId: userProfile.workspaceId,
|
||||
),
|
||||
);
|
||||
|
||||
if (state.member != null) {
|
||||
children.add(
|
||||
_LocalAIOnBoarding(
|
||||
userProfile: userProfile,
|
||||
member: state.member!,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SettingsBody(
|
||||
title: LocaleKeys.settings_aiPage_title.tr(),
|
||||
@ -116,8 +126,12 @@ class _AISearchToggle extends StatelessWidget {
|
||||
|
||||
// ignore: unused_element
|
||||
class _LocalAIOnBoarding extends StatelessWidget {
|
||||
const _LocalAIOnBoarding({required this.workspaceId});
|
||||
final String workspaceId;
|
||||
const _LocalAIOnBoarding({
|
||||
required this.userProfile,
|
||||
required this.member,
|
||||
});
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceMemberPB member;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -125,7 +139,7 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
return BillingGateGuard(
|
||||
builder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => LocalAIOnBoardingBloc(workspaceId)
|
||||
create: (context) => LocalAIOnBoardingBloc(userProfile)
|
||||
..add(const LocalAIOnBoardingEvent.started()),
|
||||
child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>(
|
||||
builder: (context, state) {
|
||||
@ -133,16 +147,20 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
if (kDebugMode || state.isPurchaseAILocal) {
|
||||
return const LocalAISetting();
|
||||
} else {
|
||||
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan
|
||||
return _UpgradeToAILocalPlan(
|
||||
onTap: () {
|
||||
context.read<SettingsDialogBloc>().add(
|
||||
const SettingsDialogEvent.setSelectedPage(
|
||||
SettingsPage.plan,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (member.role.isOwner) {
|
||||
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan
|
||||
return _UpgradeToAILocalPlan(
|
||||
onTap: () {
|
||||
context.read<SettingsDialogBloc>().add(
|
||||
const SettingsDialogEvent.setSelectedPage(
|
||||
SettingsPage.plan,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return const _AskOwnerUpgradeToLocalAI();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
@ -155,6 +173,18 @@ class _LocalAIOnBoarding extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AskOwnerUpgradeToLocalAI extends StatelessWidget {
|
||||
const _AskOwnerUpgradeToLocalAI();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyText(
|
||||
LocaleKeys.sideBar_askOwnerToUpgradeToLocalAI.tr(),
|
||||
color: AFThemeExtension.of(context).strongText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UpgradeToAILocalPlan extends StatefulWidget {
|
||||
const _UpgradeToAILocalPlan({required this.onTap});
|
||||
|
||||
|
@ -120,7 +120,7 @@ class SettingsDialog extends StatelessWidget {
|
||||
return const SettingsShortcutsView();
|
||||
case SettingsPage.ai:
|
||||
if (user.authenticator == AuthenticatorPB.AppFlowyCloud) {
|
||||
return SettingsAIView(userProfile: user);
|
||||
return SettingsAIView(userProfile: user, member: member);
|
||||
} else {
|
||||
return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud();
|
||||
}
|
||||
|
24
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
24
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -826,7 +826,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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 = "a371912c61d79fa946ec78f0cb852fdd7d391356" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "40bd6b784eced6fe43c95941420b3ce444b0a60c" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
24
frontend/appflowy_web_app/src-tauri/Cargo.lock
generated
24
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -800,7 +800,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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 = "a371912c61d79fa946ec78f0cb852fdd7d391356" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "40bd6b784eced6fe43c95941420b3ce444b0a60c" }
|
||||
|
||||
[dependencies]
|
||||
serde_json.workspace = true
|
||||
|
@ -301,7 +301,8 @@
|
||||
"askOwnerToUpgradeToAIMax": "Your workspace is running out of free AI responses. Please ask your workspace owner to upgrade the plan or purchase AI add-ons",
|
||||
"purchaseStorageSpace": "Purchase Storage Space",
|
||||
"purchaseAIResponse": "Purchase ",
|
||||
"upgradeToAILocal": "AI offline on your device"
|
||||
"askOwnerToUpgradeToLocalAI": "Ask workspace owner to enable AI On-device",
|
||||
"upgradeToAILocal": "AI On-device on your device"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
@ -654,7 +655,7 @@
|
||||
"menuLabel": "AI Settings",
|
||||
"keys": {
|
||||
"enableAISearchTitle": "AI Search",
|
||||
"aiSettingsDescription": "Select or configure AI models used on @:appName. For best performance we recommend using the default model options",
|
||||
"aiSettingsDescription": "Choose your preferred model to power AppFlowy AI. Now includes GPT 4-o, Claude 3,5, Llama 3.1, and Mistral 7B",
|
||||
"loginToEnableAIFeature": "AI features are only enabled after logging in with @:appName Cloud. If you don't have an @:appName account, go to 'My Account' to sign up",
|
||||
"llmModel": "Language Model",
|
||||
"llmModelType": "Language Model Type",
|
||||
@ -778,7 +779,7 @@
|
||||
},
|
||||
"aiOnDevice": {
|
||||
"label": "AI On-device for Mac",
|
||||
"description": "Unlock unlimited AI offline on your device",
|
||||
"description": "Unlock unlimited AI On-device on your device",
|
||||
"activeDescription": "Next invoice due on {}",
|
||||
"canceledDescription": "AI On-device for Mac will be available until {}"
|
||||
},
|
||||
|
24
frontend/rust-lib/Cargo.lock
generated
24
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -718,7 +718,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1356,7 +1356,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
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=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -5307,7 +5307,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a371912c61d79fa946ec78f0cb852fdd7d391356#a371912c61d79fa946ec78f0cb852fdd7d391356"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=40bd6b784eced6fe43c95941420b3ce444b0a60c#40bd6b784eced6fe43c95941420b3ce444b0a60c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
@ -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 = "a371912c61d79fa946ec78f0cb852fdd7d391356" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a371912c61d79fa946ec78f0cb852fdd7d391356" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "40bd6b784eced6fe43c95941420b3ce444b0a60c" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "40bd6b784eced6fe43c95941420b3ce444b0a60c" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
@ -54,6 +54,9 @@ pub struct ChatMessageMetaPB {
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub text: String,
|
||||
|
||||
#[pb(index = 4)]
|
||||
pub source: String,
|
||||
}
|
||||
|
||||
#[derive(Default, ProtoBuf, Validate, Clone, Debug)]
|
||||
|
@ -42,7 +42,7 @@ pub(crate) async fn stream_chat_message_handler(
|
||||
data: ChatMetadataData::new_text(metadata.text),
|
||||
id: metadata.id,
|
||||
name: metadata.name.clone(),
|
||||
source: metadata.name,
|
||||
source: metadata.source,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -304,6 +304,9 @@ pub enum ErrorCode {
|
||||
|
||||
#[error("AI offline not started")]
|
||||
AIOfflineNotInstalled = 105,
|
||||
|
||||
#[error("Invalid Request")]
|
||||
InvalidRequest = 106,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -15,7 +15,7 @@ impl From<AppResponseError> for FlowyError {
|
||||
AppErrorCode::MissingPayload => ErrorCode::MissingPayload,
|
||||
AppErrorCode::OpenError => ErrorCode::Internal,
|
||||
AppErrorCode::InvalidUrl => ErrorCode::InvalidURL,
|
||||
AppErrorCode::InvalidRequest => ErrorCode::InvalidParams,
|
||||
AppErrorCode::InvalidRequest => ErrorCode::InvalidRequest,
|
||||
AppErrorCode::InvalidOAuthProvider => ErrorCode::InvalidAuthConfig,
|
||||
AppErrorCode::NotLoggedIn => ErrorCode::UserUnauthorized,
|
||||
AppErrorCode::NotEnoughPermissions => ErrorCode::NotEnoughPermissions,
|
||||
|
Loading…
Reference in New Issue
Block a user