feat: regenerate ai response (#7006)

* feat: regenerate ai response

* chore: find question id instead of assuming

* chore: fix clippy

* chore: show local messages if they were there

* chore: remove duplicate code

* chore: fix loading message

* chore: revert unintended translation key removal

* chore: update translation for ai service unavailable

* chore: fix initial chat message load

---------

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Richard Shiue 2024-12-19 14:13:53 +08:00 committed by GitHub
parent 04a013f7ee
commit e73fd56152
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 479 additions and 158 deletions

View File

@ -34,7 +34,7 @@ class MobileQuickActionButton extends StatelessWidget {
enable ? null : const WidgetStatePropertyAll(Colors.transparent), enable ? null : const WidgetStatePropertyAll(Colors.transparent),
splashColor: Colors.transparent, splashColor: Colors.transparent,
child: Container( child: Container(
height: 52, height: 44,
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row( child: Row(
children: [ children: [

View File

@ -30,7 +30,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
super(ChatState.initial()) { super(ChatState.initial()) {
_startListening(); _startListening();
_dispatch(); _dispatch();
_init(); _loadMessages();
_loadSetting();
} }
final String chatId; final String chatId;
@ -59,16 +60,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
AnswerStream? answerStream; AnswerStream? answerStream;
int numSendMessage = 0; int numSendMessage = 0;
/// a counter used to determine whether the initial loading state should be
/// set to finish. It should hit two before we emit: one for the local fetch
/// and another for the server fetch.
///
/// This is to work around a bug where if an ai chat that is not yet on the
/// user local storage is opened but has messages in the server, it will
/// remain stuck on the welcome screen until the user switches to another page
/// then come back.
int initialFetchCounter = 0;
@override @override
Future<void> close() async { Future<void> close() async {
await answerStream?.dispose(); await answerStream?.dispose();
@ -86,14 +77,23 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
await chatController.insert(message, index: 0); await chatController.insert(message, index: 0);
} }
if (initialFetchCounter < 2) { switch (state.loadingState) {
initialFetchCounter++; case LoadChatMessageStatus.loading
} when chatController.messages.isEmpty:
emit(
if (state.loadingState.isLoading && initialFetchCounter >= 2) { state.copyWith(
emit( loadingState: LoadChatMessageStatus.loadingRemote,
state.copyWith(loadingState: const ChatLoadingState.finish()), ),
); );
break;
case LoadChatMessageStatus.loading:
case LoadChatMessageStatus.loadingRemote:
emit(
state.copyWith(loadingState: LoadChatMessageStatus.ready),
);
break;
default:
break;
} }
}, },
loadPreviousMessages: () { loadPreviousMessages: () {
@ -165,18 +165,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
) { ) {
numSendMessage += 1; numSendMessage += 1;
final relatedQuestionMessages = chatController.messages _clearRelatedQuestions();
.where(
(message) =>
onetimeMessageTypeFromMeta(message.metadata) ==
OnetimeShotType.relatedQuestion,
)
.toList();
for (final message in relatedQuestionMessages) {
chatController.remove(message);
}
_startStreamingMessage(message, metadata); _startStreamingMessage(message, metadata);
lastSentMessage = null; lastSentMessage = null;
@ -229,7 +218,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
answerStreamMessageId = ''; answerStreamMessageId = '';
}, },
startAnswerStreaming: (Message message) { startAnswerStreaming: (Message message) {
chatController.insert(message);
emit( emit(
state.copyWith( state.copyWith(
promptResponseState: PromptResponseState.streamingAnswer, promptResponseState: PromptResponseState.streamingAnswer,
@ -247,6 +235,17 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
), ),
); );
}, },
regenerateAnswer: (id) {
_clearRelatedQuestions();
_regenerateAnswer(id);
lastSentMessage = null;
emit(
state.copyWith(
promptResponseState: PromptResponseState.sendingQuestion,
),
);
},
didReceiveChatSettings: (settings) { didReceiveChatSettings: (settings) {
emit( emit(
state.copyWith(selectedSourceIds: settings.ragIds), state.copyWith(selectedSourceIds: settings.ragIds),
@ -348,10 +347,10 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
); );
} }
void _init() async { void _loadSetting() async {
final getChatSettingsPayload = final getChatSettingsPayload =
AIEventGetChatSettings(ChatId(value: chatId)); AIEventGetChatSettings(ChatId(value: chatId));
final getChatSettingsFuture = getChatSettingsPayload.send().fold( await getChatSettingsPayload.send().fold(
(settings) { (settings) {
if (!isClosed) { if (!isClosed) {
add(ChatEvent.didReceiveChatSettings(settings: settings)); add(ChatEvent.didReceiveChatSettings(settings: settings));
@ -359,13 +358,14 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
}, },
Log.error, Log.error,
); );
}
void _loadMessages() async {
final loadMessagesPayload = LoadNextChatMessagePB( final loadMessagesPayload = LoadNextChatMessagePB(
chatId: chatId, chatId: chatId,
limit: Int64(10), limit: Int64(10),
); );
final loadMessagesFuture = await AIEventLoadNextMessage(loadMessagesPayload).send().fold(
AIEventLoadNextMessage(loadMessagesPayload).send().fold(
(list) { (list) {
if (!isClosed) { if (!isClosed) {
final messages = list.messages.map(_createTextMessage).toList(); final messages = list.messages.map(_createTextMessage).toList();
@ -374,8 +374,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
}, },
(err) => Log.error("Failed to load messages: $err"), (err) => Log.error("Failed to load messages: $err"),
); );
await Future.wait([getChatSettingsFuture, loadMessagesFuture]);
} }
bool _isOneTimeMessage(Message message) { bool _isOneTimeMessage(Message message) {
@ -433,6 +431,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
); );
add(ChatEvent.finishSending(question)); add(ChatEvent.finishSending(question));
add(ChatEvent.receiveMessage(streamAnswer));
add(ChatEvent.startAnswerStreaming(streamAnswer)); add(ChatEvent.startAnswerStreaming(streamAnswer));
} }
}, },
@ -460,6 +459,37 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
); );
} }
void _regenerateAnswer(String answerMessageIdString) async {
final answerMessageId = Int64.tryParseInt(answerMessageIdString);
if (answerMessageId == null) {
return;
}
await answerStream?.dispose();
answerStream = AnswerStream();
final payload = RegenerateResponsePB(
chatId: chatId,
answerMessageId: answerMessageId,
answerStreamPort: Int64(answerStream!.nativePort),
);
await AIEventRegenerateResponse(payload).send().fold(
(success) {
if (!isClosed) {
final streamAnswer = _createAnswerStreamMessage(
answerStream!,
answerMessageId - 1,
);
add(ChatEvent.receiveMessage(streamAnswer));
add(ChatEvent.startAnswerStreaming(streamAnswer));
}
},
(err) => Log.error("Failed to send message: ${err.msg}"),
);
}
Message _createAnswerStreamMessage( Message _createAnswerStreamMessage(
AnswerStream stream, AnswerStream stream,
Int64 questionMessageId, Int64 questionMessageId,
@ -518,6 +548,20 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
}, },
); );
} }
void _clearRelatedQuestions() {
final relatedQuestionMessages = chatController.messages
.where(
(message) =>
onetimeMessageTypeFromMeta(message.metadata) ==
OnetimeShotType.relatedQuestion,
)
.toList();
for (final message in relatedQuestionMessages) {
chatController.remove(message);
}
}
} }
@freezed @freezed
@ -539,6 +583,9 @@ class ChatEvent with _$ChatEvent {
_FinishSendMessage; _FinishSendMessage;
const factory ChatEvent.failedSending() = _FailSendMessage; const factory ChatEvent.failedSending() = _FailSendMessage;
// regenerate
const factory ChatEvent.regenerateAnswer(String id) = _RegenerateAnswer;
// streaming answer // streaming answer
const factory ChatEvent.startAnswerStreaming(Message message) = const factory ChatEvent.startAnswerStreaming(Message message) =
_StartAnswerStreaming; _StartAnswerStreaming;
@ -567,13 +614,13 @@ class ChatEvent with _$ChatEvent {
class ChatState with _$ChatState { class ChatState with _$ChatState {
const factory ChatState({ const factory ChatState({
required List<String> selectedSourceIds, required List<String> selectedSourceIds,
required ChatLoadingState loadingState, required LoadChatMessageStatus loadingState,
required PromptResponseState promptResponseState, required PromptResponseState promptResponseState,
}) = _ChatState; }) = _ChatState;
factory ChatState.initial() => const ChatState( factory ChatState.initial() => const ChatState(
selectedSourceIds: [], selectedSourceIds: [],
loadingState: ChatLoadingState.loading(), loadingState: LoadChatMessageStatus.loading,
promptResponseState: PromptResponseState.ready, promptResponseState: PromptResponseState.ready,
); );
} }

View File

@ -132,3 +132,9 @@ const onetimeShotType = "OnetimeShotType";
OnetimeShotType? onetimeMessageTypeFromMeta(Map<String, dynamic>? metadata) { OnetimeShotType? onetimeMessageTypeFromMeta(Map<String, dynamic>? metadata) {
return metadata?[onetimeShotType]; return metadata?[onetimeShotType];
} }
enum LoadChatMessageStatus {
loading,
loadingRemote,
ready,
}

View File

@ -120,20 +120,13 @@ class _ChatContentPage extends StatelessWidget {
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: BlocBuilder<ChatBloc, ChatState>( child: BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) { builder: (context, state) {
return state.loadingState.when( return switch (state.loadingState) {
loading: () { LoadChatMessageStatus.ready => Column(
return const Center(
child: CircularProgressIndicator.adaptive(),
);
},
finish: (_) {
final chatController =
context.read<ChatBloc>().chatController;
return Column(
children: [ children: [
Expanded( Expanded(
child: Chat( child: Chat(
chatController: chatController, chatController:
context.read<ChatBloc>().chatController,
user: User(id: userProfile.id.toString()), user: User(id: userProfile.id.toString()),
darkTheme: ChatTheme.fromThemeData(Theme.of(context)), darkTheme: ChatTheme.fromThemeData(Theme.of(context)),
theme: ChatTheme.fromThemeData(Theme.of(context)), theme: ChatTheme.fromThemeData(Theme.of(context)),
@ -148,9 +141,9 @@ class _ChatContentPage extends StatelessWidget {
), ),
_buildInput(context), _buildInput(context),
], ],
); ),
}, _ => const Center(child: CircularProgressIndicator.adaptive()),
); };
}, },
), ),
), ),
@ -223,6 +216,8 @@ class _ChatContentPage extends StatelessWidget {
isLastMessage: isLastMessage, isLastMessage: isLastMessage,
onSelectedMetadata: (metadata) => onSelectedMetadata: (metadata) =>
_onSelectMetadata(context, metadata), _onSelectMetadata(context, metadata),
onRegenerate: (id) =>
context.read<ChatBloc>().add(ChatEvent.regenerateAnswer(id)),
); );
}, },
); );

View File

@ -30,12 +30,14 @@ class ChatAIMessageBubble extends StatelessWidget {
required this.child, required this.child,
required this.showActions, required this.showActions,
this.isLastMessage = false, this.isLastMessage = false,
this.onRegenerate,
}); });
final Message message; final Message message;
final Widget child; final Widget child;
final bool showActions; final bool showActions;
final bool isLastMessage; final bool isLastMessage;
final void Function(String)? onRegenerate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -60,6 +62,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapBottomActions(Widget child) { Widget _wrapBottomActions(Widget child) {
return ChatAIBottomInlineActions( return ChatAIBottomInlineActions(
message: message, message: message,
onRegenerate: onRegenerate,
child: child, child: child,
); );
} }
@ -67,6 +70,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapHover(Widget child) { Widget _wrapHover(Widget child) {
return ChatAIMessageHover( return ChatAIMessageHover(
message: message, message: message,
onRegenerate: onRegenerate,
child: child, child: child,
); );
} }
@ -74,6 +78,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapPopMenu(Widget child) { Widget _wrapPopMenu(Widget child) {
return ChatAIMessagePopup( return ChatAIMessagePopup(
message: message, message: message,
onRegenerate: onRegenerate,
child: child, child: child,
); );
} }
@ -84,10 +89,12 @@ class ChatAIBottomInlineActions extends StatelessWidget {
super.key, super.key,
required this.child, required this.child,
required this.message, required this.message,
this.onRegenerate,
}); });
final Widget child; final Widget child;
final Message message; final Message message;
final void Function(String)? onRegenerate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -107,6 +114,9 @@ class ChatAIBottomInlineActions extends StatelessWidget {
CopyButton( CopyButton(
textMessage: message as TextMessage, textMessage: message as TextMessage,
), ),
RegenerateButton(
onTap: () => onRegenerate?.call(message.id),
),
], ],
), ),
), ),
@ -121,10 +131,12 @@ class ChatAIMessageHover extends StatefulWidget {
super.key, super.key,
required this.child, required this.child,
required this.message, required this.message,
this.onRegenerate,
}); });
final Widget child; final Widget child;
final Message message; final Message message;
final void Function(String)? onRegenerate;
@override @override
State<ChatAIMessageHover> createState() => _ChatAIMessageHoverState(); State<ChatAIMessageHover> createState() => _ChatAIMessageHoverState();
@ -206,6 +218,10 @@ class _ChatAIMessageHoverState extends State<ChatAIMessageHover> {
CopyButton( CopyButton(
textMessage: widget.message as TextMessage, textMessage: widget.message as TextMessage,
), ),
RegenerateButton(
onTap: () =>
widget.onRegenerate?.call(widget.message.id),
),
], ],
) )
: null, : null,
@ -370,15 +386,44 @@ class CopyButton extends StatelessWidget {
} }
} }
class RegenerateButton extends StatelessWidget {
const RegenerateButton({
super.key,
required this.onTap,
});
final void Function() onTap;
@override
Widget build(BuildContext context) {
return FlowyTooltip(
message: LocaleKeys.chat_regenerate.tr(),
child: FlowyIconButton(
width: DesktopAIConvoSizes.actionBarIconSize,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
radius: DesktopAIConvoSizes.actionBarIconRadius,
icon: FlowySvg(
FlowySvgs.ai_undo_s,
color: Theme.of(context).hintColor,
size: const Size.square(16),
),
onPressed: onTap,
),
);
}
}
class ChatAIMessagePopup extends StatelessWidget { class ChatAIMessagePopup extends StatelessWidget {
const ChatAIMessagePopup({ const ChatAIMessagePopup({
super.key, super.key,
required this.child, required this.child,
required this.message, required this.message,
this.onRegenerate,
}); });
final Widget child; final Widget child;
final Message message; final Message message;
final void Function(String)? onRegenerate;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -393,33 +438,11 @@ class ChatAIMessagePopup extends StatelessWidget {
return Column( return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
MobileQuickActionButton( const VSpace(16.0),
onTap: () async { _copyButton(context, bottomSheetContext),
if (message is! TextMessage) { const Divider(height: 8.5, thickness: 0.5),
return; _regenerateButton(context),
} const Divider(height: 8.5, thickness: 0.5),
final textMessage = message as TextMessage;
final document = customMarkdownToDocument(textMessage.text);
await getIt<ClipboardService>().setData(
ClipboardServiceData(
plainText: textMessage.text,
inAppJson: jsonEncode(document.toJson()),
),
);
if (bottomSheetContext.mounted) {
Navigator.of(bottomSheetContext).pop();
}
if (context.mounted) {
showToastNotification(
context,
message: LocaleKeys.grid_url_copiedNotification.tr(),
);
}
},
icon: FlowySvgs.copy_s,
iconSize: const Size.square(20),
text: LocaleKeys.button_copy.tr(),
),
], ],
); );
}, },
@ -428,4 +451,46 @@ class ChatAIMessagePopup extends StatelessWidget {
child: child, child: child,
); );
} }
Widget _copyButton(BuildContext context, BuildContext bottomSheetContext) {
return MobileQuickActionButton(
onTap: () async {
if (message is! TextMessage) {
return;
}
final textMessage = message as TextMessage;
final document = customMarkdownToDocument(textMessage.text);
await getIt<ClipboardService>().setData(
ClipboardServiceData(
plainText: textMessage.text,
inAppJson: jsonEncode(document.toJson()),
),
);
if (bottomSheetContext.mounted) {
Navigator.of(bottomSheetContext).pop();
}
if (context.mounted) {
showToastNotification(
context,
message: LocaleKeys.grid_url_copiedNotification.tr(),
);
}
},
icon: FlowySvgs.copy_s,
iconSize: const Size.square(20),
text: LocaleKeys.button_copy.tr(),
);
}
Widget _regenerateButton(BuildContext context) {
return MobileQuickActionButton(
onTap: () {
onRegenerate?.call(message.id);
Navigator.of(context).pop();
},
icon: FlowySvgs.ai_undo_s,
iconSize: const Size.square(20),
text: LocaleKeys.chat_regenerate.tr(),
);
}
} }

View File

@ -33,6 +33,7 @@ class ChatAIMessageWidget extends StatelessWidget {
required this.chatId, required this.chatId,
required this.refSourceJsonString, required this.refSourceJsonString,
this.onSelectedMetadata, this.onSelectedMetadata,
this.onRegenerate,
this.isLastMessage = false, this.isLastMessage = false,
}); });
@ -45,6 +46,7 @@ class ChatAIMessageWidget extends StatelessWidget {
final String chatId; final String chatId;
final String? refSourceJsonString; final String? refSourceJsonString;
final void Function(ChatMessageRefSource metadata)? onSelectedMetadata; final void Function(ChatMessageRefSource metadata)? onSelectedMetadata;
final void Function(String messageId)? onRegenerate;
final bool isLastMessage; final bool isLastMessage;
@override @override
@ -80,6 +82,7 @@ class ChatAIMessageWidget extends StatelessWidget {
message: message, message: message,
isLastMessage: isLastMessage, isLastMessage: isLastMessage,
showActions: stream == null && state.text.isNotEmpty, showActions: stream == null && state.text.isNotEmpty,
onRegenerate: onRegenerate,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -101,11 +104,6 @@ class ChatAIMessageWidget extends StatelessWidget {
onError: (error) { onError: (error) {
return ChatErrorMessageWidget( return ChatErrorMessageWidget(
errorMessage: LocaleKeys.chat_aiServerUnavailable.tr(), errorMessage: LocaleKeys.chat_aiServerUnavailable.tr(),
onRetry: () {
context
.read<ChatAIMessageBloc>()
.add(const ChatAIMessageEvent.retry());
},
); );
}, },
onAIResponseLimit: () { onAIResponseLimit: () {

View File

@ -172,7 +172,7 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -192,7 +192,7 @@ dependencies = [
[[package]] [[package]]
name = "appflowy-ai-client" name = "appflowy-ai-client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -888,7 +888,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"again", "again",
"anyhow", "anyhow",
@ -944,7 +944,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api-entity" name = "client-api-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"collab-entity", "collab-entity",
"collab-rt-entity", "collab-rt-entity",
@ -957,7 +957,7 @@ dependencies = [
[[package]] [[package]]
name = "client-websocket" name = "client-websocket"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
@ -1257,7 +1257,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-entity" name = "collab-rt-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -1282,7 +1282,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-protocol" name = "collab-rt-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1679,7 +1679,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3268,7 +3268,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -3285,7 +3285,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3840,7 +3840,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -6576,7 +6576,7 @@ dependencies = [
[[package]] [[package]]
name = "shared-entity" name = "shared-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",

View File

@ -59,7 +59,7 @@ collab-importer = { version = "0.1" }
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "abf827f2a6bebc70ef8829909dcf6acb3ce24715" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9f57fa63d5ee039f50b0d27b860679deb14fee2e" }
[dependencies] [dependencies]
serde_json.workspace = true serde_json.workspace = true

View File

@ -163,7 +163,7 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -183,7 +183,7 @@ dependencies = [
[[package]] [[package]]
name = "appflowy-ai-client" name = "appflowy-ai-client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -877,7 +877,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"again", "again",
"anyhow", "anyhow",
@ -933,7 +933,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api-entity" name = "client-api-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"collab-entity", "collab-entity",
"collab-rt-entity", "collab-rt-entity",
@ -946,7 +946,7 @@ dependencies = [
[[package]] [[package]]
name = "client-websocket" name = "client-websocket"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
@ -1255,7 +1255,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-entity" name = "collab-rt-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -1280,7 +1280,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-protocol" name = "collab-rt-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1684,7 +1684,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3350,7 +3350,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -3367,7 +3367,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3927,7 +3927,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -6656,7 +6656,7 @@ dependencies = [
[[package]] [[package]]
name = "shared-entity" name = "shared-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",

View File

@ -58,7 +58,7 @@ collab-importer = { version = "0.1" }
# Run the script: # Run the script:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "abf827f2a6bebc70ef8829909dcf6acb3ce24715" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9f57fa63d5ee039f50b0d27b860679deb14fee2e" }
[dependencies] [dependencies]
serde_json.workspace = true serde_json.workspace = true

View File

@ -180,8 +180,8 @@
"inputLocalAIMessageHint": "Ask @:appName Local AI", "inputLocalAIMessageHint": "Ask @:appName Local AI",
"unsupportedCloudPrompt": "This feature is only available when using @:appName Cloud", "unsupportedCloudPrompt": "This feature is only available when using @:appName Cloud",
"relatedQuestion": "Suggested", "relatedQuestion": "Suggested",
"serverUnavailable": "Service Temporarily Unavailable. Please try again later.", "serverUnavailable": "Connection lost. Please check your internet and",
"aiServerUnavailable": "Connection lost. Please check your internet and", "aiServerUnavailable": "The AI service is temporarily unavailable. Please try again later.",
"retry": "Retry", "retry": "Retry",
"clickToRetry": "Click to retry", "clickToRetry": "Click to retry",
"regenerateAnswer": "Regenerate", "regenerateAnswer": "Regenerate",
@ -205,7 +205,8 @@
"questionDetail": "Hi {}! How can I help you today?", "questionDetail": "Hi {}! How can I help you today?",
"indexingFile": "Indexing {}", "indexingFile": "Indexing {}",
"generatingResponse": "Generating response", "generatingResponse": "Generating response",
"selectSources": "Select Sources" "selectSources": "Select Sources",
"regenerate": "Try again"
}, },
"trash": { "trash": {
"text": "Trash", "text": "Trash",

View File

@ -163,7 +163,7 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -183,7 +183,7 @@ dependencies = [
[[package]] [[package]]
name = "appflowy-ai-client" name = "appflowy-ai-client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -780,7 +780,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"again", "again",
"anyhow", "anyhow",
@ -836,7 +836,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api-entity" name = "client-api-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"collab-entity", "collab-entity",
"collab-rt-entity", "collab-rt-entity",
@ -849,7 +849,7 @@ dependencies = [
[[package]] [[package]]
name = "client-websocket" name = "client-websocket"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
@ -1118,7 +1118,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-entity" name = "collab-rt-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -1143,7 +1143,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-protocol" name = "collab-rt-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1389,7 +1389,7 @@ dependencies = [
"cssparser-macros", "cssparser-macros",
"dtoa-short", "dtoa-short",
"itoa", "itoa",
"phf 0.11.2", "phf 0.8.0",
"smallvec", "smallvec",
] ]
@ -1538,7 +1538,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -2982,7 +2982,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"futures-util", "futures-util",
@ -2999,7 +2999,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",
@ -3484,7 +3484,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -4499,7 +4499,7 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [ dependencies = [
"phf_macros 0.8.0", "phf_macros",
"phf_shared 0.8.0", "phf_shared 0.8.0",
"proc-macro-hack", "proc-macro-hack",
] ]
@ -4519,7 +4519,6 @@ version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [ dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2", "phf_shared 0.11.2",
] ]
@ -4587,19 +4586,6 @@ dependencies = [
"syn 1.0.109", "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]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.8.0" version = "0.8.0"
@ -5855,7 +5841,7 @@ dependencies = [
[[package]] [[package]]
name = "shared-entity" name = "shared-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=abf827f2a6bebc70ef8829909dcf6acb3ce24715#abf827f2a6bebc70ef8829909dcf6acb3ce24715" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=9f57fa63d5ee039f50b0d27b860679deb14fee2e#9f57fa63d5ee039f50b0d27b860679deb14fee2e"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",

View File

@ -107,8 +107,8 @@ dashmap = "6.0.1"
# Run the script.add_workspace_members: # Run the script.add_workspace_members:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "abf827f2a6bebc70ef8829909dcf6acb3ce24715" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9f57fa63d5ee039f50b0d27b860679deb14fee2e" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "abf827f2a6bebc70ef8829909dcf6acb3ce24715" } client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "9f57fa63d5ee039f50b0d27b860679deb14fee2e" }
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0

View File

@ -70,6 +70,13 @@ pub trait ChatCloudService: Send + Sync + 'static {
limit: u64, limit: u64,
) -> Result<RepeatedChatMessage, FlowyError>; ) -> Result<RepeatedChatMessage, FlowyError>;
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError>;
async fn get_related_message( async fn get_related_message(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -37,6 +37,8 @@ pub trait AIQueryService: Send + Sync + 'static {
parent_view_id: &str, parent_view_id: &str,
chat_id: &str, chat_id: &str,
) -> Result<Vec<String>, FlowyError>; ) -> Result<Vec<String>, FlowyError>;
async fn sync_rag_documents(&self, rag_ids: Vec<String>) -> Result<(), FlowyError>;
} }
pub struct AIManager { pub struct AIManager {
@ -102,6 +104,7 @@ impl AIManager {
if self.local_ai_controller.is_running() { if self.local_ai_controller.is_running() {
self.local_ai_controller.open_chat(chat_id); self.local_ai_controller.open_chat(chat_id);
} }
Ok(()) Ok(())
} }
@ -193,6 +196,22 @@ impl AIManager {
Ok(question) Ok(question)
} }
pub async fn stream_regenerate_response(
&self,
chat_id: &str,
answer_message_id: i64,
answer_stream_port: i64,
) -> FlowyResult<()> {
let chat = self.get_or_create_chat_instance(chat_id).await?;
let question_message_id = chat
.get_question_id_from_answer_id(answer_message_id)
.await?;
chat
.stream_regenerate_response(question_message_id, answer_stream_port)
.await?;
Ok(())
}
pub async fn get_or_create_chat_instance(&self, chat_id: &str) -> Result<Arc<Chat>, FlowyError> { pub async fn get_or_create_chat_instance(&self, chat_id: &str) -> Result<Arc<Chat>, FlowyError> {
let chat = self.chats.get(chat_id).as_deref().cloned(); let chat = self.chats.get(chat_id).as_deref().cloned();
match chat { match chat {

View File

@ -4,7 +4,10 @@ use crate::entities::{
}; };
use crate::middleware::chat_service_mw::AICloudServiceMiddleware; use crate::middleware::chat_service_mw::AICloudServiceMiddleware;
use crate::notification::{chat_notification_builder, ChatNotification}; use crate::notification::{chat_notification_builder, ChatNotification};
use crate::persistence::{insert_chat_messages, select_chat_messages, ChatMessageTable}; use crate::persistence::{
insert_chat_messages, select_chat_messages, select_message_where_match_reply_message_id,
ChatMessageTable,
};
use crate::stream_message::StreamMessage; use crate::stream_message::StreamMessage;
use allo_isolate::Isolate; use allo_isolate::Isolate;
use flowy_ai_pub::cloud::{ use flowy_ai_pub::cloud::{
@ -138,9 +141,60 @@ impl Chat {
// Save message to disk // Save message to disk
save_and_notify_message(uid, &self.chat_id, &self.user_service, question.clone())?; save_and_notify_message(uid, &self.chat_id, &self.user_service, question.clone())?;
self.stream_response(
answer_stream_port,
answer_stream_buffer,
uid,
workspace_id,
question.message_id,
);
let question_pb = ChatMessagePB::from(question);
Ok(question_pb)
}
#[instrument(level = "info", skip_all, err)]
pub async fn stream_regenerate_response(
&self,
question_id: i64,
answer_stream_port: i64,
) -> FlowyResult<()> {
trace!(
"[Chat] regenerate and stream chat message: chat_id={}",
self.chat_id,
);
// clear
self
.stop_stream
.store(false, std::sync::atomic::Ordering::SeqCst);
self.stream_buffer.lock().await.clear();
let answer_stream_buffer = self.stream_buffer.clone();
let uid = self.user_service.user_id()?;
let workspace_id = self.user_service.workspace_id()?;
self.stream_response(
answer_stream_port,
answer_stream_buffer,
uid,
workspace_id,
question_id,
);
Ok(())
}
fn stream_response(
&self,
answer_stream_port: i64,
answer_stream_buffer: Arc<Mutex<StringBuffer>>,
uid: i64,
workspace_id: String,
question_id: i64,
) {
let stop_stream = self.stop_stream.clone(); let stop_stream = self.stop_stream.clone();
let chat_id = self.chat_id.clone(); let chat_id = self.chat_id.clone();
let question_id = question.message_id;
let cloud_service = self.chat_service.clone(); let cloud_service = self.chat_service.clone();
let user_service = self.user_service.clone(); let user_service = self.user_service.clone();
tokio::spawn(async move { tokio::spawn(async move {
@ -217,9 +271,6 @@ impl Chat {
save_and_notify_message(uid, &chat_id, &user_service, answer)?; save_and_notify_message(uid, &chat_id, &user_service, answer)?;
Ok::<(), FlowyError>(()) Ok::<(), FlowyError>(())
}); });
let question_pb = ChatMessagePB::from(question);
Ok(question_pb)
} }
/// Load chat messages for a given `chat_id`. /// Load chat messages for a given `chat_id`.
@ -393,6 +444,30 @@ impl Chat {
Ok(()) Ok(())
} }
pub async fn get_question_id_from_answer_id(
&self,
answer_message_id: i64,
) -> Result<i64, FlowyError> {
let conn = self.user_service.sqlite_connection(self.uid)?;
let local_result = select_message_where_match_reply_message_id(conn, answer_message_id)?
.map(|message| message.message_id);
if let Some(message_id) = local_result {
return Ok(message_id);
}
let workspace_id = self.user_service.workspace_id()?;
let chat_id = self.chat_id.clone();
let cloud_service = self.chat_service.clone();
let question = cloud_service
.get_question_from_answer_id(&workspace_id, &chat_id, answer_message_id)
.await?;
Ok(question.message_id)
}
pub async fn get_related_question( pub async fn get_related_question(
&self, &self,
message_id: i64, message_id: i64,

View File

@ -71,6 +71,19 @@ pub struct StreamChatPayloadPB {
pub metadata: Vec<ChatMessageMetaPB>, pub metadata: Vec<ChatMessageMetaPB>,
} }
#[derive(Default, ProtoBuf, Validate, Clone, Debug)]
pub struct RegenerateResponsePB {
#[pb(index = 1)]
#[validate(custom(function = "required_not_empty_str"))]
pub chat_id: String,
#[pb(index = 2)]
pub answer_message_id: i64,
#[pb(index = 3)]
pub answer_stream_port: i64,
}
#[derive(Default, ProtoBuf, Validate, Clone, Debug)] #[derive(Default, ProtoBuf, Validate, Clone, Debug)]
pub struct ChatMessageMetaPB { pub struct ChatMessageMetaPB {
#[pb(index = 1)] #[pb(index = 1)]

View File

@ -77,6 +77,24 @@ pub(crate) async fn stream_chat_message_handler(
data_result_ok(result) data_result_ok(result)
} }
#[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn regenerate_response_handler(
data: AFPluginData<RegenerateResponsePB>,
ai_manager: AFPluginState<Weak<AIManager>>,
) -> FlowyResult<()> {
let data = data.try_into_inner()?;
let ai_manager = upgrade_ai_manager(ai_manager)?;
ai_manager
.stream_regenerate_response(
&data.chat_id,
data.answer_message_id,
data.answer_stream_port,
)
.await?;
Ok(())
}
#[tracing::instrument(level = "debug", skip_all, err)] #[tracing::instrument(level = "debug", skip_all, err)]
pub(crate) async fn load_prev_message_handler( pub(crate) async fn load_prev_message_handler(
data: AFPluginData<LoadPrevChatMessagePB>, data: AFPluginData<LoadPrevChatMessagePB>,

View File

@ -59,6 +59,7 @@ pub fn init(ai_manager: Weak<AIManager>) -> AFPlugin {
.event(AIEvent::GetChatInfo, create_chat_context_handler) .event(AIEvent::GetChatInfo, create_chat_context_handler)
.event(AIEvent::GetChatSettings, get_chat_settings_handler) .event(AIEvent::GetChatSettings, get_chat_settings_handler)
.event(AIEvent::UpdateChatSettings, update_chat_settings_handler) .event(AIEvent::UpdateChatSettings, update_chat_settings_handler)
.event(AIEvent::RegenerateResponse, regenerate_response_handler)
} }
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
@ -150,4 +151,7 @@ pub enum AIEvent {
#[event(input = "UpdateChatSettingsPB")] #[event(input = "UpdateChatSettingsPB")]
UpdateChatSettings = 26, UpdateChatSettings = 26,
#[event(input = "RegenerateResponsePB")]
RegenerateResponse = 27,
} }

View File

@ -224,6 +224,18 @@ impl ChatCloudService for AICloudServiceMiddleware {
.await .await
} }
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
answer_id: i64,
) -> Result<ChatMessage, FlowyError> {
self
.cloud_service
.get_question_from_answer_id(workspace_id, chat_id, answer_id)
.await
}
async fn get_related_message( async fn get_related_message(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -82,3 +82,13 @@ pub fn select_single_message(
.optional()?; .optional()?;
Ok(message) Ok(message)
} }
pub fn select_message_where_match_reply_message_id(
mut conn: DBConnection,
answer_message_id_val: i64,
) -> QueryResult<Option<ChatMessageTable>> {
dsl::chat_message_table
.filter(chat_message_table::reply_message_id.eq(answer_message_id_val))
.first::<ChatMessageTable>(&mut *conn)
.optional()
}

View File

@ -56,6 +56,15 @@ impl AIQueryService for ChatQueryServiceImpl {
Ok(ids) Ok(ids)
} }
async fn sync_rag_documents(&self, rag_ids: Vec<String>) -> Result<(), FlowyError> {
for rag_id in rag_ids.iter() {
if let Some(_query_collab) = self.folder_query.get_collab(rag_id).await {
// TODO(nathan): sync
}
}
Ok(())
}
} }
struct ChatUserServiceImpl(Weak<AuthenticateUser>); struct ChatUserServiceImpl(Weak<AuthenticateUser>);

View File

@ -34,7 +34,7 @@ use tokio::sync::RwLock;
use crate::integrate::server::ServerProvider; use crate::integrate::server::ServerProvider;
use collab_plugins::local_storage::kv::KVTransactionDB; use collab_plugins::local_storage::kv::KVTransactionDB;
use flowy_folder_pub::query::FolderQueryService; use flowy_folder_pub::query::{FolderQueryService, QueryCollab};
use lib_infra::async_trait::async_trait; use lib_infra::async_trait::async_trait;
pub struct FolderDepsResolver(); pub struct FolderDepsResolver();
@ -707,4 +707,9 @@ impl FolderQueryService for FolderQueryServiceImpl {
}, },
} }
} }
async fn get_collab(&self, _object_id: &str) -> Option<QueryCollab> {
// TODO(nathan): return query collab
None
}
} }

View File

@ -715,6 +715,19 @@ impl ChatCloudService for ServerProvider {
.await .await
} }
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
self
.get_server()?
.chat_service()
.get_question_from_answer_id(workspace_id, chat_id, answer_message_id)
.await
}
async fn get_related_message( async fn get_related_message(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -356,6 +356,9 @@ pub enum ErrorCode {
#[error("Requested namespace has one or more invalid characters")] #[error("Requested namespace has one or more invalid characters")]
CustomNamespaceInvalidCharacter = 122, CustomNamespaceInvalidCharacter = 122,
#[error("Requested namespace has one or more invalid characters")]
AIServiceUnavailable = 123,
} }
impl ErrorCode { impl ErrorCode {

View File

@ -1,6 +1,5 @@
use client_api::error::{AppResponseError, ErrorCode as AppErrorCode};
use crate::{ErrorCode, FlowyError}; use crate::{ErrorCode, FlowyError};
use client_api::error::{AppResponseError, ErrorCode as AppErrorCode};
impl From<AppResponseError> for FlowyError { impl From<AppResponseError> for FlowyError {
fn from(error: AppResponseError) -> Self { fn from(error: AppResponseError) -> Self {
@ -37,6 +36,7 @@ impl From<AppResponseError> for FlowyError {
AppErrorCode::PublishNameInvalidCharacter => ErrorCode::PublishNameInvalidCharacter, AppErrorCode::PublishNameInvalidCharacter => ErrorCode::PublishNameInvalidCharacter,
AppErrorCode::PublishNameTooLong => ErrorCode::PublishNameTooLong, AppErrorCode::PublishNameTooLong => ErrorCode::PublishNameTooLong,
AppErrorCode::CustomNamespaceInvalidCharacter => ErrorCode::CustomNamespaceInvalidCharacter, AppErrorCode::CustomNamespaceInvalidCharacter => ErrorCode::CustomNamespaceInvalidCharacter,
AppErrorCode::AIServiceUnavailable => ErrorCode::AIServiceUnavailable,
_ => ErrorCode::Internal, _ => ErrorCode::Internal,
}; };

View File

@ -1,6 +1,14 @@
use collab::entity::EncodedCollab;
use collab_entity::CollabType;
use collab_folder::ViewLayout; use collab_folder::ViewLayout;
use lib_infra::async_trait::async_trait; use lib_infra::async_trait::async_trait;
pub struct QueryCollab {
pub name: String,
pub collab_type: CollabType,
pub encoded_collab: EncodedCollab,
}
#[async_trait] #[async_trait]
pub trait FolderQueryService: Send + Sync + 'static { pub trait FolderQueryService: Send + Sync + 'static {
async fn get_sibling_ids_with_view_layout( async fn get_sibling_ids_with_view_layout(
@ -8,4 +16,6 @@ pub trait FolderQueryService: Send + Sync + 'static {
parent_view_id: &str, parent_view_id: &str,
view_layout: ViewLayout, view_layout: ViewLayout,
) -> Vec<String>; ) -> Vec<String>;
async fn get_collab(&self, object_id: &str) -> Option<QueryCollab>;
} }

View File

@ -99,11 +99,11 @@ where
message_id: i64, message_id: i64,
) -> Result<StreamAnswer, FlowyError> { ) -> Result<StreamAnswer, FlowyError> {
let try_get_client = self.inner.try_get_client(); let try_get_client = self.inner.try_get_client();
let stream = try_get_client? let result = try_get_client?
.stream_answer_v2(workspace_id, chat_id, message_id) .stream_answer_v2(workspace_id, chat_id, message_id)
.await .await;
.map_err(FlowyError::from)?
.map_err(FlowyError::from); let stream = result.map_err(FlowyError::from)?.map_err(FlowyError::from);
Ok(stream.boxed()) Ok(stream.boxed())
} }
@ -137,6 +137,22 @@ where
Ok(resp) Ok(resp)
} }
async fn get_question_from_answer_id(
&self,
workspace_id: &str,
chat_id: &str,
answer_message_id: i64,
) -> Result<ChatMessage, FlowyError> {
let try_get_client = self.inner.try_get_client()?;
let resp = try_get_client
.get_question_message_from_answer_id(workspace_id, chat_id, answer_message_id)
.await
.map_err(FlowyError::from)?
.ok_or_else(FlowyError::record_not_found)?;
Ok(resp)
}
async fn get_related_message( async fn get_related_message(
&self, &self,
workspace_id: &str, workspace_id: &str,

View File

@ -64,6 +64,15 @@ impl ChatCloudService for DefaultChatCloudServiceImpl {
Err(FlowyError::not_support().with_context("Chat is not supported in local server.")) Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
} }
async fn get_question_from_answer_id(
&self,
_workspace_id: &str,
_chat_id: &str,
_answer_id: i64,
) -> Result<ChatMessage, FlowyError> {
Err(FlowyError::not_support().with_context("Chat is not supported in local server."))
}
async fn get_related_message( async fn get_related_message(
&self, &self,
_workspace_id: &str, _workspace_id: &str,