chore: show attachment on local ai (#5929)

* chore: show attachment on local ai

* chore: fix compile
This commit is contained in:
Nathan.fooo 2024-08-12 09:21:44 +08:00 committed by GitHub
parent 23997e977c
commit f7adcae8ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 74 additions and 32 deletions

View File

@ -16,13 +16,13 @@ part 'chat_ai_message_bloc.freezed.dart';
class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> { class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> {
ChatAIMessageBloc({ ChatAIMessageBloc({
dynamic message, dynamic message,
String? metadata, String? refSourceJsonString,
required this.chatId, required this.chatId,
required this.questionId, required this.questionId,
}) : super( }) : super(
ChatAIMessageState.initial( ChatAIMessageState.initial(
message, message,
messageRefSourceFromString(metadata), messageReferenceSource(refSourceJsonString),
), ),
) { ) {
if (state.stream != null) { if (state.stream != null) {

View File

@ -374,7 +374,10 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
metadata: await metadataPBFromMetadata(metadata), metadata: await metadataPBFromMetadata(metadata),
); );
final questionStreamMessage = _createQuestionStreamMessage(questionStream); final questionStreamMessage = _createQuestionStreamMessage(
questionStream,
metadata,
);
add(ChatEvent.receveMessage(questionStreamMessage)); add(ChatEvent.receveMessage(questionStreamMessage));
// Stream message to the server // Stream message to the server
@ -432,14 +435,24 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
); );
} }
Message _createQuestionStreamMessage(QuestionStream stream) { Message _createQuestionStreamMessage(
QuestionStream stream,
Map<String, dynamic>? sentMetadata,
) {
questionStreamMessageId = nanoid(); questionStreamMessageId = nanoid();
final Map<String, dynamic> metadata = {};
// if (sentMetadata != null) {
// metadata[messageMetadataJsonStringKey] = sentMetadata;
// }
metadata["$QuestionStream"] = stream;
metadata["chatId"] = chatId;
metadata[messageChatFileListKey] =
chatFilesFromMessageMetadata(sentMetadata);
return TextMessage( return TextMessage(
author: User(id: state.userProfile.id.toString()), author: User(id: state.userProfile.id.toString()),
metadata: { metadata: metadata,
"$QuestionStream": stream,
"chatId": chatId,
},
id: questionStreamMessageId, id: questionStreamMessageId,
createdAt: DateTime.now().millisecondsSinceEpoch, createdAt: DateTime.now().millisecondsSinceEpoch,
text: '', text: '',
@ -460,7 +473,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
text: message.content, text: message.content,
createdAt: message.createdAt.toInt() * 1000, createdAt: message.createdAt.toInt() * 1000,
metadata: { metadata: {
messageMetadataKey: message.metadata, messageRefSourceJsonStringKey: message.metadata,
}, },
); );
} }

View File

@ -15,8 +15,14 @@ part 'chat_entity.freezed.dart';
const sendMessageErrorKey = "sendMessageError"; const sendMessageErrorKey = "sendMessageError";
const systemUserId = "system"; const systemUserId = "system";
const aiResponseUserId = "0"; const aiResponseUserId = "0";
const messageMetadataKey = "metadata";
const messageQuestionIdKey = "question"; /// `messageRefSourceJsonStringKey` is the key used for metadata that contains the reference source of a message.
/// Each message may include this information.
/// - When used in a sent message, it indicates that the message includes an attachment.
/// - When used in a received message, it indicates the AI reference sources used to answer a question.
const messageRefSourceJsonStringKey = "ref_source_json_string";
const messageChatFileListKey = "chat_files";
const messageQuestionIdKey = "question_id";
@JsonSerializable() @JsonSerializable()
class ChatMessageRefSource { class ChatMessageRefSource {

View File

@ -32,7 +32,12 @@ List<ChatFile> chatFilesFromMetadataString(String? s) {
final metadataJson = jsonDecode(s); final metadataJson = jsonDecode(s);
if (metadataJson is Map<String, dynamic>) { if (metadataJson is Map<String, dynamic>) {
return _parseChatFile(metadataJson); final file = chatFileFromMap(metadataJson);
if (file != null) {
return [file];
} else {
return [];
}
} else if (metadataJson is List) { } else if (metadataJson is List) {
return metadataJson return metadataJson
.map((e) => e as Map<String, dynamic>) .map((e) => e as Map<String, dynamic>)
@ -46,11 +51,6 @@ List<ChatFile> chatFilesFromMetadataString(String? s) {
} }
} }
List<ChatFile> _parseChatFile(Map<String, dynamic> map) {
final file = chatFileFromMap(map);
return file != null ? [file] : [];
}
ChatFile? chatFileFromMap(Map<String, dynamic>? map) { ChatFile? chatFileFromMap(Map<String, dynamic>? map) {
if (map == null) return null; if (map == null) return null;
@ -63,7 +63,7 @@ ChatFile? chatFileFromMap(Map<String, dynamic>? map) {
return ChatFile.fromFilePath(filePath); return ChatFile.fromFilePath(filePath);
} }
List<ChatMessageRefSource> messageRefSourceFromString(String? s) { List<ChatMessageRefSource> messageReferenceSource(String? s) {
if (s == null || s.isEmpty || s == "null") { if (s == null || s.isEmpty || s == "null") {
return []; return [];
} }
@ -139,3 +139,18 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
return metadata; return metadata;
} }
List<ChatFile> chatFilesFromMessageMetadata(
Map<String, dynamic>? map,
) {
final List<ChatFile> metadata = [];
if (map != null) {
for (final entry in map.entries) {
if (entry.value is ChatFile) {
metadata.add(entry.value);
}
}
}
return metadata;
}

View File

@ -25,7 +25,7 @@ class AnswerStream {
} else if (event.startsWith("metadata:")) { } else if (event.startsWith("metadata:")) {
if (_onMetadata != null) { if (_onMetadata != null) {
final s = event.substring(9); final s = event.substring(9);
_onMetadata!(messageRefSourceFromString(s)); _onMetadata!(messageReferenceSource(s));
} }
} else if (event == "AI_RESPONSE_LIMIT") { } else if (event == "AI_RESPONSE_LIMIT") {
if (_onAIResponseLimit != null) { if (_onAIResponseLimit != null) {

View File

@ -11,11 +11,10 @@ class ChatUserMessageBubbleBloc
extends Bloc<ChatUserMessageBubbleEvent, ChatUserMessageBubbleState> { extends Bloc<ChatUserMessageBubbleEvent, ChatUserMessageBubbleState> {
ChatUserMessageBubbleBloc({ ChatUserMessageBubbleBloc({
required Message message, required Message message,
required String? metadata,
}) : super( }) : super(
ChatUserMessageBubbleState.initial( ChatUserMessageBubbleState.initial(
message, message,
chatFilesFromMetadataString(metadata), _getFiles(message.metadata),
), ),
) { ) {
on<ChatUserMessageBubbleEvent>( on<ChatUserMessageBubbleEvent>(
@ -28,6 +27,19 @@ class ChatUserMessageBubbleBloc
} }
} }
List<ChatFile> _getFiles(Map<String, dynamic>? metadata) {
if (metadata == null) {
return [];
}
final refSourceMetadata = metadata[messageRefSourceJsonStringKey] as String?;
final files = metadata[messageChatFileListKey] as List<ChatFile>?;
if (refSourceMetadata != null) {
return chatFilesFromMetadataString(refSourceMetadata);
}
return files ?? [];
}
@freezed @freezed
class ChatUserMessageBubbleEvent with _$ChatUserMessageBubbleEvent { class ChatUserMessageBubbleEvent with _$ChatUserMessageBubbleEvent {
const factory ChatUserMessageBubbleEvent.initial() = Initial; const factory ChatUserMessageBubbleEvent.initial() = Initial;

View File

@ -320,17 +320,16 @@ class _ChatContentPageState extends State<_ChatContentPage> {
Widget _buildTextMessage(BuildContext context, TextMessage message) { Widget _buildTextMessage(BuildContext context, TextMessage message) {
if (message.author.id == _user.id) { if (message.author.id == _user.id) {
final stream = message.metadata?["$QuestionStream"]; final stream = message.metadata?["$QuestionStream"];
final metadata = message.metadata?[messageMetadataKey] as String?;
return ChatUserMessageWidget( return ChatUserMessageWidget(
key: ValueKey(message.id), key: ValueKey(message.id),
user: message.author, user: message.author,
message: stream is QuestionStream ? stream : message.text, message: stream is QuestionStream ? stream : message.text,
metadata: metadata,
); );
} else { } else {
final stream = message.metadata?["$AnswerStream"]; final stream = message.metadata?["$AnswerStream"];
final questionId = message.metadata?[messageQuestionIdKey]; final questionId = message.metadata?[messageQuestionIdKey];
final metadata = message.metadata?[messageMetadataKey] as String?; final refSourceJsonString =
message.metadata?[messageRefSourceJsonStringKey] as String?;
return ChatAIMessageWidget( return ChatAIMessageWidget(
user: message.author, user: message.author,
messageUserId: message.id, messageUserId: message.id,
@ -338,7 +337,7 @@ class _ChatContentPageState extends State<_ChatContentPage> {
key: ValueKey(message.id), key: ValueKey(message.id),
questionId: questionId, questionId: questionId,
chatId: widget.view.id, chatId: widget.view.id,
metadata: metadata, refSourceJsonString: refSourceJsonString,
onSelectedMetadata: (ChatMessageRefSource metadata) { onSelectedMetadata: (ChatMessageRefSource metadata) {
context.read<ChatSidePannelBloc>().add( context.read<ChatSidePannelBloc>().add(
ChatSidePannelEvent.selectedMetadata(metadata), ChatSidePannelEvent.selectedMetadata(metadata),

View File

@ -22,7 +22,7 @@ class ChatAIMessageWidget extends StatelessWidget {
required this.message, required this.message,
required this.questionId, required this.questionId,
required this.chatId, required this.chatId,
required this.metadata, required this.refSourceJsonString,
required this.onSelectedMetadata, required this.onSelectedMetadata,
}); });
@ -33,7 +33,7 @@ class ChatAIMessageWidget extends StatelessWidget {
final dynamic message; final dynamic message;
final Int64? questionId; final Int64? questionId;
final String chatId; final String chatId;
final String? metadata; final String? refSourceJsonString;
final void Function(ChatMessageRefSource metadata) onSelectedMetadata; final void Function(ChatMessageRefSource metadata) onSelectedMetadata;
@override @override
@ -41,7 +41,7 @@ class ChatAIMessageWidget extends StatelessWidget {
return BlocProvider( return BlocProvider(
create: (context) => ChatAIMessageBloc( create: (context) => ChatAIMessageBloc(
message: message, message: message,
metadata: metadata, refSourceJsonString: refSourceJsonString,
chatId: chatId, chatId: chatId,
questionId: questionId, questionId: questionId,
)..add(const ChatAIMessageEvent.initial()), )..add(const ChatAIMessageEvent.initial()),

View File

@ -29,12 +29,10 @@ class ChatUserMessageBubble extends StatelessWidget {
.read<ChatMemberBloc>() .read<ChatMemberBloc>()
.add(ChatMemberEvent.getMemberInfo(message.author.id)); .add(ChatMemberEvent.getMemberInfo(message.author.id));
} }
final metadata = message.metadata?[messageMetadataKey] as String?;
return BlocProvider( return BlocProvider(
create: (context) => ChatUserMessageBubbleBloc( create: (context) => ChatUserMessageBubbleBloc(
message: message, message: message,
metadata: metadata,
), ),
child: BlocBuilder<ChatUserMessageBubbleBloc, ChatUserMessageBubbleState>( child: BlocBuilder<ChatUserMessageBubbleBloc, ChatUserMessageBubbleState>(
builder: (context, state) { builder: (context, state) {

View File

@ -10,12 +10,10 @@ class ChatUserMessageWidget extends StatelessWidget {
super.key, super.key,
required this.user, required this.user,
required this.message, required this.message,
required this.metadata,
}); });
final User user; final User user;
final dynamic message; final dynamic message;
final String? metadata;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View File

@ -63,6 +63,7 @@ impl AICloudServiceMiddleware {
let _ = index_process_sink let _ = index_process_sink
.send(StreamMessage::IndexStart.to_string()) .send(StreamMessage::IndexStart.to_string())
.await; .await;
self self
.local_llm_controller .local_llm_controller
.index_message_metadata(chat_id, metadata_list, index_process_sink) .index_message_metadata(chat_id, metadata_list, index_process_sink)