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> {
ChatAIMessageBloc({
dynamic message,
String? metadata,
String? refSourceJsonString,
required this.chatId,
required this.questionId,
}) : super(
ChatAIMessageState.initial(
message,
messageRefSourceFromString(metadata),
messageReferenceSource(refSourceJsonString),
),
) {
if (state.stream != null) {

View File

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

View File

@ -15,8 +15,14 @@ part 'chat_entity.freezed.dart';
const sendMessageErrorKey = "sendMessageError";
const systemUserId = "system";
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()
class ChatMessageRefSource {

View File

@ -32,7 +32,12 @@ List<ChatFile> chatFilesFromMetadataString(String? s) {
final metadataJson = jsonDecode(s);
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) {
return metadataJson
.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) {
if (map == null) return null;
@ -63,7 +63,7 @@ ChatFile? chatFileFromMap(Map<String, dynamic>? map) {
return ChatFile.fromFilePath(filePath);
}
List<ChatMessageRefSource> messageRefSourceFromString(String? s) {
List<ChatMessageRefSource> messageReferenceSource(String? s) {
if (s == null || s.isEmpty || s == "null") {
return [];
}
@ -139,3 +139,18 @@ Future<List<ChatMessageMetaPB>> metadataPBFromMetadata(
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:")) {
if (_onMetadata != null) {
final s = event.substring(9);
_onMetadata!(messageRefSourceFromString(s));
_onMetadata!(messageReferenceSource(s));
}
} else if (event == "AI_RESPONSE_LIMIT") {
if (_onAIResponseLimit != null) {

View File

@ -11,11 +11,10 @@ class ChatUserMessageBubbleBloc
extends Bloc<ChatUserMessageBubbleEvent, ChatUserMessageBubbleState> {
ChatUserMessageBubbleBloc({
required Message message,
required String? metadata,
}) : super(
ChatUserMessageBubbleState.initial(
message,
chatFilesFromMetadataString(metadata),
_getFiles(message.metadata),
),
) {
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
class ChatUserMessageBubbleEvent with _$ChatUserMessageBubbleEvent {
const factory ChatUserMessageBubbleEvent.initial() = Initial;

View File

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

View File

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

View File

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

View File

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

View File

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