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),
splashColor: Colors.transparent,
child: Container(
height: 52,
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [

View File

@ -30,7 +30,8 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
super(ChatState.initial()) {
_startListening();
_dispatch();
_init();
_loadMessages();
_loadSetting();
}
final String chatId;
@ -59,16 +60,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
AnswerStream? answerStream;
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
Future<void> close() async {
await answerStream?.dispose();
@ -86,14 +77,23 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
await chatController.insert(message, index: 0);
}
if (initialFetchCounter < 2) {
initialFetchCounter++;
}
if (state.loadingState.isLoading && initialFetchCounter >= 2) {
emit(
state.copyWith(loadingState: const ChatLoadingState.finish()),
);
switch (state.loadingState) {
case LoadChatMessageStatus.loading
when chatController.messages.isEmpty:
emit(
state.copyWith(
loadingState: LoadChatMessageStatus.loadingRemote,
),
);
break;
case LoadChatMessageStatus.loading:
case LoadChatMessageStatus.loadingRemote:
emit(
state.copyWith(loadingState: LoadChatMessageStatus.ready),
);
break;
default:
break;
}
},
loadPreviousMessages: () {
@ -165,18 +165,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
) {
numSendMessage += 1;
final relatedQuestionMessages = chatController.messages
.where(
(message) =>
onetimeMessageTypeFromMeta(message.metadata) ==
OnetimeShotType.relatedQuestion,
)
.toList();
for (final message in relatedQuestionMessages) {
chatController.remove(message);
}
_clearRelatedQuestions();
_startStreamingMessage(message, metadata);
lastSentMessage = null;
@ -229,7 +218,6 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
answerStreamMessageId = '';
},
startAnswerStreaming: (Message message) {
chatController.insert(message);
emit(
state.copyWith(
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) {
emit(
state.copyWith(selectedSourceIds: settings.ragIds),
@ -348,10 +347,10 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
);
}
void _init() async {
void _loadSetting() async {
final getChatSettingsPayload =
AIEventGetChatSettings(ChatId(value: chatId));
final getChatSettingsFuture = getChatSettingsPayload.send().fold(
await getChatSettingsPayload.send().fold(
(settings) {
if (!isClosed) {
add(ChatEvent.didReceiveChatSettings(settings: settings));
@ -359,13 +358,14 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
},
Log.error,
);
}
void _loadMessages() async {
final loadMessagesPayload = LoadNextChatMessagePB(
chatId: chatId,
limit: Int64(10),
);
final loadMessagesFuture =
AIEventLoadNextMessage(loadMessagesPayload).send().fold(
await AIEventLoadNextMessage(loadMessagesPayload).send().fold(
(list) {
if (!isClosed) {
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"),
);
await Future.wait([getChatSettingsFuture, loadMessagesFuture]);
}
bool _isOneTimeMessage(Message message) {
@ -433,6 +431,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
);
add(ChatEvent.finishSending(question));
add(ChatEvent.receiveMessage(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(
AnswerStream stream,
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
@ -539,6 +583,9 @@ class ChatEvent with _$ChatEvent {
_FinishSendMessage;
const factory ChatEvent.failedSending() = _FailSendMessage;
// regenerate
const factory ChatEvent.regenerateAnswer(String id) = _RegenerateAnswer;
// streaming answer
const factory ChatEvent.startAnswerStreaming(Message message) =
_StartAnswerStreaming;
@ -567,13 +614,13 @@ class ChatEvent with _$ChatEvent {
class ChatState with _$ChatState {
const factory ChatState({
required List<String> selectedSourceIds,
required ChatLoadingState loadingState,
required LoadChatMessageStatus loadingState,
required PromptResponseState promptResponseState,
}) = _ChatState;
factory ChatState.initial() => const ChatState(
selectedSourceIds: [],
loadingState: ChatLoadingState.loading(),
loadingState: LoadChatMessageStatus.loading,
promptResponseState: PromptResponseState.ready,
);
}

View File

@ -132,3 +132,9 @@ const onetimeShotType = "OnetimeShotType";
OnetimeShotType? onetimeMessageTypeFromMeta(Map<String, dynamic>? metadata) {
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),
child: BlocBuilder<ChatBloc, ChatState>(
builder: (context, state) {
return state.loadingState.when(
loading: () {
return const Center(
child: CircularProgressIndicator.adaptive(),
);
},
finish: (_) {
final chatController =
context.read<ChatBloc>().chatController;
return Column(
return switch (state.loadingState) {
LoadChatMessageStatus.ready => Column(
children: [
Expanded(
child: Chat(
chatController: chatController,
chatController:
context.read<ChatBloc>().chatController,
user: User(id: userProfile.id.toString()),
darkTheme: ChatTheme.fromThemeData(Theme.of(context)),
theme: ChatTheme.fromThemeData(Theme.of(context)),
@ -148,9 +141,9 @@ class _ChatContentPage extends StatelessWidget {
),
_buildInput(context),
],
);
},
);
),
_ => const Center(child: CircularProgressIndicator.adaptive()),
};
},
),
),
@ -223,6 +216,8 @@ class _ChatContentPage extends StatelessWidget {
isLastMessage: isLastMessage,
onSelectedMetadata: (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.showActions,
this.isLastMessage = false,
this.onRegenerate,
});
final Message message;
final Widget child;
final bool showActions;
final bool isLastMessage;
final void Function(String)? onRegenerate;
@override
Widget build(BuildContext context) {
@ -60,6 +62,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapBottomActions(Widget child) {
return ChatAIBottomInlineActions(
message: message,
onRegenerate: onRegenerate,
child: child,
);
}
@ -67,6 +70,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapHover(Widget child) {
return ChatAIMessageHover(
message: message,
onRegenerate: onRegenerate,
child: child,
);
}
@ -74,6 +78,7 @@ class ChatAIMessageBubble extends StatelessWidget {
Widget _wrapPopMenu(Widget child) {
return ChatAIMessagePopup(
message: message,
onRegenerate: onRegenerate,
child: child,
);
}
@ -84,10 +89,12 @@ class ChatAIBottomInlineActions extends StatelessWidget {
super.key,
required this.child,
required this.message,
this.onRegenerate,
});
final Widget child;
final Message message;
final void Function(String)? onRegenerate;
@override
Widget build(BuildContext context) {
@ -107,6 +114,9 @@ class ChatAIBottomInlineActions extends StatelessWidget {
CopyButton(
textMessage: message as TextMessage,
),
RegenerateButton(
onTap: () => onRegenerate?.call(message.id),
),
],
),
),
@ -121,10 +131,12 @@ class ChatAIMessageHover extends StatefulWidget {
super.key,
required this.child,
required this.message,
this.onRegenerate,
});
final Widget child;
final Message message;
final void Function(String)? onRegenerate;
@override
State<ChatAIMessageHover> createState() => _ChatAIMessageHoverState();
@ -206,6 +218,10 @@ class _ChatAIMessageHoverState extends State<ChatAIMessageHover> {
CopyButton(
textMessage: widget.message as TextMessage,
),
RegenerateButton(
onTap: () =>
widget.onRegenerate?.call(widget.message.id),
),
],
)
: 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 {
const ChatAIMessagePopup({
super.key,
required this.child,
required this.message,
this.onRegenerate,
});
final Widget child;
final Message message;
final void Function(String)? onRegenerate;
@override
Widget build(BuildContext context) {
@ -393,33 +438,11 @@ class ChatAIMessagePopup extends StatelessWidget {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
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(),
),
const VSpace(16.0),
_copyButton(context, bottomSheetContext),
const Divider(height: 8.5, thickness: 0.5),
_regenerateButton(context),
const Divider(height: 8.5, thickness: 0.5),
],
);
},
@ -428,4 +451,46 @@ class ChatAIMessagePopup extends StatelessWidget {
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.refSourceJsonString,
this.onSelectedMetadata,
this.onRegenerate,
this.isLastMessage = false,
});
@ -45,6 +46,7 @@ class ChatAIMessageWidget extends StatelessWidget {
final String chatId;
final String? refSourceJsonString;
final void Function(ChatMessageRefSource metadata)? onSelectedMetadata;
final void Function(String messageId)? onRegenerate;
final bool isLastMessage;
@override
@ -80,6 +82,7 @@ class ChatAIMessageWidget extends StatelessWidget {
message: message,
isLastMessage: isLastMessage,
showActions: stream == null && state.text.isNotEmpty,
onRegenerate: onRegenerate,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -101,11 +104,6 @@ class ChatAIMessageWidget extends StatelessWidget {
onError: (error) {
return ChatErrorMessageWidget(
errorMessage: LocaleKeys.chat_aiServerUnavailable.tr(),
onRetry: () {
context
.read<ChatAIMessageBloc>()
.add(const ChatAIMessageEvent.retry());
},
);
},
onAIResponseLimit: () {

View File

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

View File

@ -59,7 +59,7 @@ collab-importer = { version = "0.1" }
# Run the script:
# 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]
serde_json.workspace = true

View File

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

View File

@ -58,7 +58,7 @@ collab-importer = { version = "0.1" }
# Run the script:
# 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]
serde_json.workspace = true

View File

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

View File

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

View File

@ -107,8 +107,8 @@ dashmap = "6.0.1"
# 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 = "abf827f2a6bebc70ef8829909dcf6acb3ce24715" }
client-api-entity = { 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 = "9f57fa63d5ee039f50b0d27b860679deb14fee2e" }
[profile.dev]
opt-level = 0

View File

@ -70,6 +70,13 @@ pub trait ChatCloudService: Send + Sync + 'static {
limit: u64,
) -> 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(
&self,
workspace_id: &str,

View File

@ -37,6 +37,8 @@ pub trait AIQueryService: Send + Sync + 'static {
parent_view_id: &str,
chat_id: &str,
) -> Result<Vec<String>, FlowyError>;
async fn sync_rag_documents(&self, rag_ids: Vec<String>) -> Result<(), FlowyError>;
}
pub struct AIManager {
@ -102,6 +104,7 @@ impl AIManager {
if self.local_ai_controller.is_running() {
self.local_ai_controller.open_chat(chat_id);
}
Ok(())
}
@ -193,6 +196,22 @@ impl AIManager {
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> {
let chat = self.chats.get(chat_id).as_deref().cloned();
match chat {

View File

@ -4,7 +4,10 @@ use crate::entities::{
};
use crate::middleware::chat_service_mw::AICloudServiceMiddleware;
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 allo_isolate::Isolate;
use flowy_ai_pub::cloud::{
@ -138,9 +141,60 @@ impl Chat {
// Save message to disk
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 chat_id = self.chat_id.clone();
let question_id = question.message_id;
let cloud_service = self.chat_service.clone();
let user_service = self.user_service.clone();
tokio::spawn(async move {
@ -217,9 +271,6 @@ impl Chat {
save_and_notify_message(uid, &chat_id, &user_service, answer)?;
Ok::<(), FlowyError>(())
});
let question_pb = ChatMessagePB::from(question);
Ok(question_pb)
}
/// Load chat messages for a given `chat_id`.
@ -393,6 +444,30 @@ impl Chat {
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(
&self,
message_id: i64,

View File

@ -71,6 +71,19 @@ pub struct StreamChatPayloadPB {
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)]
pub struct ChatMessageMetaPB {
#[pb(index = 1)]

View File

@ -77,6 +77,24 @@ pub(crate) async fn stream_chat_message_handler(
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)]
pub(crate) async fn load_prev_message_handler(
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::GetChatSettings, get_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)]
@ -150,4 +151,7 @@ pub enum AIEvent {
#[event(input = "UpdateChatSettingsPB")]
UpdateChatSettings = 26,
#[event(input = "RegenerateResponsePB")]
RegenerateResponse = 27,
}

View File

@ -224,6 +224,18 @@ impl ChatCloudService for AICloudServiceMiddleware {
.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(
&self,
workspace_id: &str,

View File

@ -82,3 +82,13 @@ pub fn select_single_message(
.optional()?;
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)
}
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>);

View File

@ -34,7 +34,7 @@ use tokio::sync::RwLock;
use crate::integrate::server::ServerProvider;
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;
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
}
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(
&self,
workspace_id: &str,

View File

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

View File

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

View File

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

View File

@ -99,11 +99,11 @@ where
message_id: i64,
) -> Result<StreamAnswer, FlowyError> {
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)
.await
.map_err(FlowyError::from)?
.map_err(FlowyError::from);
.await;
let stream = result.map_err(FlowyError::from)?.map_err(FlowyError::from);
Ok(stream.boxed())
}
@ -137,6 +137,22 @@ where
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(
&self,
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."))
}
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(
&self,
_workspace_id: &str,