Skip to content

Commit

Permalink
Merge pull request #54 from mylxsw/feature/group-chat
Browse files Browse the repository at this point in the history
Feature/group chat
  • Loading branch information
mylxsw authored Oct 29, 2023
2 parents 02ed770 + 7b5f59b commit 0da7e32
Show file tree
Hide file tree
Showing 136 changed files with 6,362 additions and 3,645 deletions.
1 change: 1 addition & 0 deletions lib/bloc/background_image_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:askaide/repo/api_server.dart';
import 'package:askaide/repo/model/misc.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

Expand Down
3 changes: 2 additions & 1 deletion lib/bloc/chat_chat_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:askaide/helper/constant.dart';
import 'package:askaide/repo/api_server.dart';
import 'package:askaide/repo/chat_message_repo.dart';
import 'package:askaide/repo/model/chat_history.dart';
import 'package:askaide/repo/model/misc.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

Expand All @@ -15,7 +16,7 @@ class ChatChatBloc extends Bloc<ChatChatEvent, ChatChatState> {
on<ChatChatLoadRecentHistories>((event, emit) async {
final histories = await _chatMessageRepository.recentChatHistories(
chatAnywhereRoomId,
30,
4,
userId: APIServer().localUserID(),
);

Expand Down
28 changes: 24 additions & 4 deletions lib/bloc/chat_message_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import 'package:askaide/repo/model/message.dart';
import 'package:askaide/repo/model/room.dart';
import 'package:askaide/repo/openai_repo.dart';
import 'package:askaide/repo/settings_repo.dart';
import 'package:dart_openai/openai.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

Expand Down Expand Up @@ -268,7 +269,7 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {

// 更新 Room 最后活跃时间
// 这里没有使用 await,因为不需要等待更新完成,让 room 的更新异步的去处理吧
if (!Ability().supportAPIServer()) {
if (!Ability().enableAPIServer()) {
chatMsgRepo.updateRoomLastActiveTime(roomId);
}

Expand Down Expand Up @@ -305,6 +306,7 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
// 等待监听机器人应答消息
final queue = GracefulQueue<ChatStreamRespData>();
try {
RequestFailedException? error;
var listener = queue.listen(const Duration(milliseconds: 10), (items) {
final systemCmds = items.where((e) => e.role == 'system').toList();
if (systemCmds.isNotEmpty) {
Expand Down Expand Up @@ -335,6 +337,13 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
.map((e) => e.content)
.join('');
emit(ChatMessageUpdated(waitMessage, processing: true));

// 失败处理
for (var e in items) {
if (e.code != null && e.code! > 0) {
error = RequestFailedException(e.error ?? '请求处理失败', e.code!);
}
}
});

await ModelResolver.instance
Expand All @@ -348,6 +357,15 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {

await listener;

waitMessage.text = waitMessage.text.trim();
if (waitMessage.text.isEmpty) {
error = RequestFailedException('机器人没有回答任何内容', 500);
}

if (error != null) {
throw error!;
}

// 机器人应答完成,将最后一条机器人应答消息更新到数据库,替换掉思考中消息
waitMessage.isReady = true;
await chatMsgRepo.updateMessage(roomId, waitMessage.id!, waitMessage);
Expand Down Expand Up @@ -388,11 +406,13 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
chatHistory: localChatHistory,
));
} catch (e) {
final error = resolveErrorMessage(e, isChat: true);
await chatMsgRepo.updateMessagePart(
roomId,
sentMessageId,
[
MessagePart('status', 2),
MessagePart('extra', jsonEncode({'error': error.toString()})),
],
);

Expand All @@ -403,7 +423,7 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
waitMessage.id!,
Message(
Role.receiver,
AppLocale.robotHasSomeError,
error.toString(),
id: waitMessage.id,
ts: DateTime.now(),
type: MessageType.system,
Expand All @@ -423,7 +443,7 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
userId: APIServer().localUserID(),
chatHistoryId: localChatHistoryId,
),
error: resolveErrorMessage(e),
error: error,
chatHistory: localChatHistory,
));

Expand All @@ -439,7 +459,7 @@ class ChatMessageBloc extends BlocExt<ChatMessageEvent, ChatMessageState> {
Future<Room?> queryRoomById(
ChatMessageRepository chatMsgRepo, int roomId) async {
Room? room;
if (Ability().supportAPIServer()) {
if (Ability().enableAPIServer()) {
final roomInServer = await APIServer().room(roomId: roomId);
room = Room(
roomInServer.name,
Expand Down
11 changes: 8 additions & 3 deletions lib/bloc/free_count_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:askaide/helper/ability.dart';
import 'package:askaide/repo/api_server.dart';
import 'package:askaide/repo/model/misc.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

Expand All @@ -12,8 +13,11 @@ class FreeCountBloc extends Bloc<FreeCountEvent, FreeCountState> {
FreeCountBloc() : super(FreeCountInitial()) {
// 重新加载所有的模型免费使用次数
on<FreeCountReloadAllEvent>((event, emit) async {
if (Ability().supportLocalOpenAI() || !Ability().supportAPIServer()) {
emit(FreeCountLoadedState(counts: counts));
if (!Ability().enableAPIServer()) {
emit(FreeCountLoadedState(
counts: counts,
needSignin: event.checkSigninStatus,
));
return;
}

Expand All @@ -23,7 +27,8 @@ class FreeCountBloc extends Bloc<FreeCountEvent, FreeCountState> {

// 重新加载指定模型的免费使用次数
on<FreeCountReloadEvent>((event, emit) async {
if (Ability().supportLocalOpenAI() || !Ability().supportAPIServer()) {
if (Ability().usingLocalOpenAIModel(event.model) ||
!Ability().enableAPIServer()) {
emit(FreeCountLoadedState(counts: counts));
return;
}
Expand Down
6 changes: 5 additions & 1 deletion lib/bloc/free_count_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ class FreeCountReloadEvent extends FreeCountEvent {
FreeCountReloadEvent({required this.model});
}

class FreeCountReloadAllEvent extends FreeCountEvent {}
class FreeCountReloadAllEvent extends FreeCountEvent {
final bool checkSigninStatus;

FreeCountReloadAllEvent({this.checkSigninStatus = false});
}
3 changes: 2 additions & 1 deletion lib/bloc/free_count_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ final class FreeCountInitial extends FreeCountState {}

class FreeCountLoadedState extends FreeCountState {
final List<FreeModelCount> counts;
final bool needSignin;

FreeModelCount? model(String model) {
model = model.split(':').last;
Expand All @@ -19,5 +20,5 @@ class FreeCountLoadedState extends FreeCountState {
return null;
}

FreeCountLoadedState({required this.counts});
FreeCountLoadedState({required this.counts, this.needSignin = false});
}
5 changes: 4 additions & 1 deletion lib/bloc/gallery_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ class GalleryBloc extends Bloc<GalleryEvent, GalleryState> {
id: event.id,
);

emit(GalleryItemLoaded(item: res));
emit(GalleryItemLoaded(
item: res.item,
isInternalUser: res.isInternalUser,
));
});
}
}
3 changes: 2 additions & 1 deletion lib/bloc/gallery_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class GalleryLoaded extends GalleryState {

class GalleryItemLoaded extends GalleryState {
final CreativeGallery item;
final bool isInternalUser;

GalleryItemLoaded({required this.item});
GalleryItemLoaded({required this.item, this.isInternalUser = false});
}
197 changes: 197 additions & 0 deletions lib/bloc/group_chat_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import 'dart:convert';

import 'package:askaide/helper/cache.dart';
import 'package:askaide/helper/logger.dart';
import 'package:askaide/page/component/chat/message_state_manager.dart';
import 'package:askaide/repo/api_server.dart';
import 'package:askaide/repo/model/group.dart';
import 'package:askaide/repo/model/message.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';

part 'group_chat_event.dart';
part 'group_chat_state.dart';

class GroupChatBloc extends Bloc<GroupChatEvent, GroupChatState> {
var messages = <GroupMessage>[];
final MessageStateManager stateManager;

GroupChatBloc({required this.stateManager}) : super(GroupChatInitial()) {
// 加载聊天组
on<GroupChatLoadEvent>((event, emit) async {
final group =
await APIServer().chatGroup(event.groupId, cache: !event.forceUpdate);
final states = await stateManager.loadRoomStates(event.groupId);

emit(GroupChatLoaded(
group: group,
states: states,
defaultChatMembers: await loadDefaultChatMembers(event.groupId),
));
});

// 加载聊天组聊天记录
on<GroupChatMessagesLoadEvent>((event, emit) async {
if (event.isInitRequest) {
try {
final cached =
await Cache().stringGet(key: 'group:speed:${event.groupId}');
if (cached != null) {
final messages = (jsonDecode(cached) as List<dynamic>)
.map((e) => GroupMessage.fromJson(e))
.toList();

emit(GroupChatMessagesLoaded(messages: messages));
}
} catch (e) {
Logger.instance.e(e);
}
}

await refreshGroupMessages(
event.groupId,
startId: event.startId,
forceRefresh: true,
);

emit(GroupChatMessagesLoaded(messages: messages));
});

// 发送聊天组消息
on<GroupChatSendEvent>((event, emit) async {
try {
final resp = await APIServer().chatGroupSendMessage(
event.groupId,
GroupChatSendRequest(
message: event.message,
memberIds: event.members,
),
);

Logger.instance.d(resp.toJson());

// 记录默认聊天成员
updateDefaultChatMembers(
event.groupId,
resp.tasks.map((e) => e.memberId).toList(),
).then((members) {
emit(GroupDefaultMemberSelected(members));
});

await refreshGroupMessages(
event.groupId,
startId: 0,
forceRefresh: true,
);
emit(GroupChatMessagesLoaded(messages: messages));
} catch (e) {
await refreshGroupMessages(
event.groupId,
startId: 0,
forceRefresh: true,
);
emit(GroupChatMessagesLoaded(messages: messages, error: e));
}
});

// 发送系统消息
on<GroupChatSendSystemEvent>((event, emit) async {
try {
final resp = await APIServer().chatGroupSendSystemMessage(
event.groupId,
messageType: event.type.getTypeText(),
message: event.message,
);

Logger.instance.d(resp.toJson());
} finally {
await refreshGroupMessages(
event.groupId,
startId: 0,
forceRefresh: true,
);
emit(GroupChatMessagesLoaded(messages: messages));
}
});

// 更新聊天组消息状态
on<GroupChatUpdateMessageStatusEvent>((event, emit) async {
final waitMessageIds = messages
.where((msg) => msg.status == groupMessageStatusWaiting)
.map((msg) => msg.id)
.toList();

if (waitMessageIds.isEmpty) {
return;
}

final resp = await APIServer()
.chatGroupMessageStatus(event.groupId, waitMessageIds);
final newMessageStatusMap = <int, GroupMessage>{};
for (var msg in resp) {
newMessageStatusMap[msg.id] = msg;
}

for (var i = 0; i < messages.length; i++) {
final msg = messages[i];
if (newMessageStatusMap.containsKey(msg.id)) {
messages[i] = newMessageStatusMap[msg.id]!;
}
}

emit(GroupChatMessagesLoaded(messages: messages));
});

// 清空聊天组消息
on<GroupChatDeleteAllEvent>((event, emit) async {
await APIServer().chatGroupDeleteAllMessages(event.groupId);
messages.clear();
emit(GroupChatMessagesLoaded(messages: messages));
});

// 删除聊天组消息
on<GroupChatDeleteEvent>((event, emit) async {
await APIServer().chatGroupDeleteMessage(event.groupId, event.messageId);
messages.removeWhere((msg) => msg.id == event.messageId);
emit(GroupChatMessagesLoaded(messages: messages));
});
}

refreshGroupMessages(
int groupId, {
int startId = 0,
bool forceRefresh = false,
}) async {
final data = await APIServer()
.chatGroupMessages(groupId, startId: startId, cache: !forceRefresh);
messages = data.data.reversed.toList();

if (startId == 0) {
Cache()
.setString(key: 'group:speed:$groupId', value: jsonEncode(messages));
}
}

Future<List<int>> loadDefaultChatMembers(int groupId) async {
final defaultMembers =
await Cache().stringGet(key: 'group:$groupId:default-members');

return (defaultMembers ?? '')
.split(',')
.map((e) => int.tryParse(e) ?? 0)
.where((e) => e > 0)
.toList();
}

Future<List<int>> updateDefaultChatMembers(
int groupId, List<int> members) async {
// 记录默认聊天成员
await Cache().setString(
key: 'group:$groupId:default-members',
value: members.join(','),
duration: const Duration(days: 365),
);

return members;
}
}
Loading

0 comments on commit 0da7e32

Please sign in to comment.