From c5a162f532164a770dcc16992852a4a53ffa8f4a Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 18 Oct 2023 18:00:11 +0800 Subject: [PATCH 01/28] update --- lib/bloc/background_image_bloc.dart | 1 + lib/bloc/chat_chat_bloc.dart | 1 + lib/bloc/free_count_bloc.dart | 1 + lib/bloc/group_chat_bloc.dart | 58 ++ lib/bloc/group_chat_event.dart | 26 + lib/bloc/group_chat_state.dart | 22 + lib/bloc/room_bloc.dart | 1 + lib/bloc/version_bloc.dart | 1 + lib/helper/http.dart | 19 + lib/main.dart | 25 + lib/page/chat_anywhere.dart | 2 +- lib/page/chat_chat.dart | 1 + lib/page/chat_screen.dart | 2 +- lib/page/component/chat/chat_preview.dart | 51 +- lib/page/component/chat/empty.dart | 2 +- lib/page/component/prompt_tags_selector.dart | 1 + .../creative_island_create_page.dart | 1 + lib/page/draw/draw_create.dart | 1 + lib/page/group/chat.dart | 512 +++++++++++++ lib/page/quota_usage_statistics.dart | 1 + lib/page/setting_screen.dart | 12 + lib/repo/api_server.dart | 698 ++---------------- lib/repo/model/group.dart | 227 ++++++ lib/repo/model/message.dart | 4 + lib/repo/model/misc.dart | 636 ++++++++++++++++ lib/repo/model/room.dart | 6 +- 26 files changed, 1664 insertions(+), 648 deletions(-) create mode 100644 lib/bloc/group_chat_bloc.dart create mode 100644 lib/bloc/group_chat_event.dart create mode 100644 lib/bloc/group_chat_state.dart create mode 100644 lib/page/group/chat.dart create mode 100644 lib/repo/model/group.dart create mode 100644 lib/repo/model/misc.dart diff --git a/lib/bloc/background_image_bloc.dart b/lib/bloc/background_image_bloc.dart index ec78c8dc..257f0cdd 100644 --- a/lib/bloc/background_image_bloc.dart +++ b/lib/bloc/background_image_bloc.dart @@ -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'; diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index 32357523..9f279ff1 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -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'; diff --git a/lib/bloc/free_count_bloc.dart b/lib/bloc/free_count_bloc.dart index 9181ac86..a22a25fe 100644 --- a/lib/bloc/free_count_bloc.dart +++ b/lib/bloc/free_count_bloc.dart @@ -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'; diff --git a/lib/bloc/group_chat_bloc.dart b/lib/bloc/group_chat_bloc.dart new file mode 100644 index 00000000..efc69749 --- /dev/null +++ b/lib/bloc/group_chat_bloc.dart @@ -0,0 +1,58 @@ +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:bloc/bloc.dart'; +import 'package:meta/meta.dart'; + +part 'group_chat_event.dart'; +part 'group_chat_state.dart'; + +class GroupChatBloc extends Bloc { + var messages = []; + final MessageStateManager stateManager; + + GroupChatBloc({required this.stateManager}) : super(GroupChatInitial()) { + // 加载聊天组 + on((event, emit) async { + final group = await APIServer().chatGroup(event.groupId); + final states = await stateManager.loadRoomStates(event.groupId); + emit(GroupChatLoaded(group: group, states: states)); + }); + + // 加载聊天组聊天记录 + on((event, emit) async { + await refreshGroupMessages( + event.groupId, + page: event.page, + perPage: event.perPage, + ); + + emit(GroupChatMessagesLoaded(messages: messages)); + }); + + // 发送聊天组消息 + on((event, emit) async { + final List requestMessages = [ + GroupChatSendRequestMessage(role: "user", content: event.message) + ]; + final resp = await APIServer().chatGroupSendMessage( + event.groupId, + GroupChatSendRequest( + messages: requestMessages, memberIds: event.members), + ); + + await refreshGroupMessages(event.groupId, page: 1, perPage: 100); + emit(GroupChatMessagesLoaded(messages: messages)); + }); + } + + refreshGroupMessages(int groupId, {int page = 1, int perPage = 100}) async { + final data = await APIServer().chatGroupMessages( + groupId, + page: page, + perPage: perPage, + ); + + messages = data.data.reversed.toList(); + } +} diff --git a/lib/bloc/group_chat_event.dart b/lib/bloc/group_chat_event.dart new file mode 100644 index 00000000..7ae0e578 --- /dev/null +++ b/lib/bloc/group_chat_event.dart @@ -0,0 +1,26 @@ +part of 'group_chat_bloc.dart'; + +@immutable +sealed class GroupChatEvent {} + +class GroupChatLoadEvent extends GroupChatEvent { + final int groupId; + + GroupChatLoadEvent(this.groupId); +} + +class GroupChatMessagesLoadEvent extends GroupChatEvent { + final int groupId; + final int page; + final int perPage; + + GroupChatMessagesLoadEvent(this.groupId, {this.page = 1, this.perPage = 100}); +} + +class GroupChatSendEvent extends GroupChatEvent { + final int groupId; + final String message; + final List members; + + GroupChatSendEvent(this.groupId, this.message, this.members); +} diff --git a/lib/bloc/group_chat_state.dart b/lib/bloc/group_chat_state.dart new file mode 100644 index 00000000..3d9b959a --- /dev/null +++ b/lib/bloc/group_chat_state.dart @@ -0,0 +1,22 @@ +part of 'group_chat_bloc.dart'; + +@immutable +sealed class GroupChatState {} + +final class GroupChatInitial extends GroupChatState {} + +class GroupChatLoaded extends GroupChatState { + final ChatGroup group; + final Map states; + + GroupChatLoaded({required this.group, required this.states}); +} + +class GroupChatMessagesLoaded extends GroupChatState { + final List messages; + + bool get hasWaitTasks => + messages.any((element) => element.status == groupMessageStatusWaiting); + + GroupChatMessagesLoaded({required this.messages}); +} diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index b2d724d0..1c95b0f4 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -3,6 +3,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:askaide/bloc/bloc_manager.dart'; diff --git a/lib/bloc/version_bloc.dart b/lib/bloc/version_bloc.dart index eef8e992..de07f2e3 100644 --- a/lib/bloc/version_bloc.dart +++ b/lib/bloc/version_bloc.dart @@ -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'; diff --git a/lib/helper/http.dart b/lib/helper/http.dart index 6c6a66a1..bf300015 100644 --- a/lib/helper/http.dart +++ b/lib/helper/http.dart @@ -88,6 +88,25 @@ class HttpClient { return resp; } + static Future postJSON( + String url, { + Map? queryParameters, + Map? data, + Options? options, + }) async { + final resp = await dio.post( + url, + queryParameters: queryParameters, + data: data, + options: options, + ); + // print("======================="); + // print("request: $url"); + // print("response: ${resp.data}"); + + return resp; + } + static Future put( String url, { Map? queryParameters, diff --git a/lib/main.dart b/lib/main.dart index dcdbcbc5..e33f6581 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/bloc/creative_island_bloc.dart'; import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/bloc/gallery_bloc.dart'; +import 'package:askaide/bloc/group_chat_bloc.dart'; import 'package:askaide/bloc/payment_bloc.dart'; import 'package:askaide/bloc/version_bloc.dart'; import 'package:askaide/helper/ability.dart'; @@ -33,6 +34,7 @@ import 'package:askaide/page/creative_island/creative_island_history_all.dart'; import 'package:askaide/page/creative_island/creative_island_history_preview.dart'; import 'package:askaide/page/custom_home_models.dart'; import 'package:askaide/page/free_statistics.dart'; +import 'package:askaide/page/group/chat.dart'; import 'package:askaide/page/lab/creative_models.dart'; import 'package:askaide/page/destroy_account.dart'; import 'package:askaide/page/diagnosis.dart'; @@ -811,6 +813,29 @@ class MyApp extends StatefulWidget { CustomHomeModelsPage(setting: settingRepo), ), ), + GoRoute( + name: 'group-chat-chat', + path: '/group-chat/:group_id/chat', + pageBuilder: (context, state) { + final groupId = int.tryParse(state.pathParameters['group_id']!); + + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + ], + child: GroupChatPage( + setting: settingRepo, + stateManager: messageStateManager, + groupId: groupId!, + ), + ), + ); + }, + ), ], ) ], diff --git a/lib/page/chat_anywhere.dart b/lib/page/chat_anywhere.dart index 2b262862..1955d6fc 100644 --- a/lib/page/chat_anywhere.dart +++ b/lib/page/chat_anywhere.dart @@ -17,8 +17,8 @@ import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; -import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/message.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/chat_chat.dart b/lib/page/chat_chat.dart index 1e2eea3a..03564121 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat_chat.dart @@ -21,6 +21,7 @@ import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/chat_history.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/chat_screen.dart b/lib/page/chat_screen.dart index 218c1dc8..7a1981c6 100644 --- a/lib/page/chat_screen.dart +++ b/lib/page/chat_screen.dart @@ -21,8 +21,8 @@ import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; import 'package:askaide/page/component/chat/chat_input.dart'; import 'package:askaide/page/component/chat/chat_preview.dart'; -import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/message.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index e91731cf..49085a74 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -29,6 +29,7 @@ class ChatPreview extends StatefulWidget { final MessageStateManager stateManager; final List? helpWidgets; final Widget? robotAvatar; + final bool supportBloc; final void Function(Message message)? onSpeakEvent; final void Function(Message message)? onResentEvent; @@ -43,6 +44,7 @@ class ChatPreview extends StatefulWidget { this.helpWidgets, this.onSpeakEvent, this.onResentEvent, + this.supportBloc = true, }); @override @@ -103,25 +105,38 @@ class _ChatPreviewState extends State { // 消息主体部分 Expanded( - child: BlocBuilder( - buildWhen: (previous, current) => - (current is ChatMessageUpdated && - current.message.id == message.message.id), - builder: (context, state) { - return Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 10, - ), - child: _buildMessageBox( - context, - customColors, - _resolveMessage(state, message), - message.state, + child: widget.supportBloc + ? BlocBuilder( + buildWhen: (previous, current) => + (current is ChatMessageUpdated && + current.message.id == message.message.id), + builder: (context, state) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: _buildMessageBox( + context, + customColors, + _resolveMessage(state, message), + message.state, + ), + ); + }, + ) + : Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: _buildMessageBox( + context, + customColors, + message.message, + message.state, + ), ), - ); - }, - ), ), ], ), diff --git a/lib/page/component/chat/empty.dart b/lib/page/component/chat/empty.dart index 22124810..fb366ead 100644 --- a/lib/page/component/chat/empty.dart +++ b/lib/page/component/chat/empty.dart @@ -1,5 +1,5 @@ import 'package:askaide/page/theme/custom_theme.dart'; -import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; class EmptyPreview extends StatefulWidget { diff --git a/lib/page/component/prompt_tags_selector.dart b/lib/page/component/prompt_tags_selector.dart index d9217aa0..3ea17522 100644 --- a/lib/page/component/prompt_tags_selector.dart +++ b/lib/page/component/prompt_tags_selector.dart @@ -2,6 +2,7 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/page/creative_island/creative_island_create_page.dart b/lib/page/creative_island/creative_island_create_page.dart index 5927fc64..84551588 100644 --- a/lib/page/creative_island/creative_island_create_page.dart +++ b/lib/page/creative_island/creative_island_create_page.dart @@ -22,6 +22,7 @@ import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/creative_island_repo.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:file_picker/file_picker.dart'; diff --git a/lib/page/draw/draw_create.dart b/lib/page/draw/draw_create.dart index 2e1351b3..a459227c 100644 --- a/lib/page/draw/draw_create.dart +++ b/lib/page/draw/draw_create.dart @@ -22,6 +22,7 @@ import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/cupertino.dart'; diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart new file mode 100644 index 00000000..997a097d --- /dev/null +++ b/lib/page/group/chat.dart @@ -0,0 +1,512 @@ +import 'package:askaide/bloc/group_chat_bloc.dart'; +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/haptic_feedback.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/audio_player.dart'; +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/chat/help_tips.dart'; +import 'package:askaide/page/component/chat/message_state_manager.dart'; +import 'package:askaide/page/component/enhanced_popup_menu.dart'; +import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/share.dart'; +import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/chat/chat_input.dart'; +import 'package:askaide/page/component/chat/chat_preview.dart'; +import 'package:askaide/repo/model/group.dart'; +import 'package:askaide/repo/model/message.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; + +class GroupChatPage extends StatefulWidget { + final SettingRepository setting; + final int groupId; + final MessageStateManager stateManager; + + const GroupChatPage({ + super.key, + required this.setting, + required this.groupId, + required this.stateManager, + }); + + @override + State createState() => _GroupChatPageState(); +} + +class _GroupChatPageState extends State { + final ScrollController _scrollController = ScrollController(); + final ValueNotifier _inputEnabled = ValueNotifier(true); + final ChatPreviewController _chatPreviewController = ChatPreviewController(); + final AudioPlayerController _audioPlayerController = + AudioPlayerController(useRemoteAPI: false); + bool showAudioPlayer = false; + + List selectedMemberIds = []; + + @override + void initState() { + super.initState(); + + context.read().add(GroupChatLoadEvent(widget.groupId)); + + _chatPreviewController.addListener(() { + setState(() {}); + }); + + _audioPlayerController.onPlayStopped = () { + setState(() { + showAudioPlayer = false; + }); + }; + _audioPlayerController.onPlayAudioStarted = () { + setState(() { + showAudioPlayer = true; + }); + }; + } + + @override + void dispose() { + _scrollController.dispose(); + _chatPreviewController.dispose(); + _audioPlayerController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + + return BackgroundContainer( + setting: widget.setting, + child: Scaffold( + appBar: _buildAppBar(context, customColors), + backgroundColor: Colors.transparent, + body: _buildChatComponents(customColors), + ), + ); + } + + Widget _buildChatComponents(CustomColors customColors) { + return BlocConsumer( + listenWhen: (previous, current) => current is GroupChatLoaded, + listener: (context, state) { + if (state is GroupChatLoaded) { + // 默认选中的成员 + selectedMemberIds = state.group.members.map((e) => e.id!).toList(); + // 加载聊天记录列表 + context + .read() + .add(GroupChatMessagesLoadEvent(widget.groupId)); + } + }, + buildWhen: (previous, current) => current is GroupChatLoaded, + builder: (context, room) { + if (room is GroupChatLoaded) { + return SafeArea( + top: false, + bottom: false, + child: Column( + children: [ + // 语音输出中提示 + if (showAudioPlayer) + EnhancedAudioPlayer(controller: _audioPlayerController), + // 聊天内容窗口 + Expanded( + child: _buildChatPreviewArea( + room, + customColors, + _chatPreviewController.selectMode, + ), + ), + + // 聊天输入窗口 + Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + color: customColors.chatInputPanelBackground, + ), + child: SafeArea( + child: _chatPreviewController.selectMode + ? buildSelectModeToolbars( + context, + _chatPreviewController, + customColors, + ) + : ChatInput( + enableNotifier: _inputEnabled, + enableImageUpload: false, + onSubmit: _handleSubmit, + onNewChat: () => handleResetContext(context), + hintText: '有问题尽管问我', + onVoiceRecordTappedEvent: () { + _audioPlayerController.stop(); + }, + ), + ), + ), + ], + ), + ); + } else { + return Container(); + } + }, + ); + } + + BlocConsumer _buildChatPreviewArea( + GroupChatLoaded group, + CustomColors customColors, + bool selectMode, + ) { + return BlocConsumer( + listenWhen: (previous, current) => current is GroupChatMessagesLoaded, + listener: (context, state) { + if (state is GroupChatMessagesLoaded) { + // 聊天内容窗口滚动到底部 + if (!state.hasWaitTasks && _scrollController.hasClients) { + _scrollController.animateTo( + 0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOut, + ); + } + + if (state.hasWaitTasks && _inputEnabled.value) { + // 聊天回复中时,禁止输入框编辑 + setState(() { + _inputEnabled.value = false; + }); + } else if (!state.hasWaitTasks && !_inputEnabled.value) { + // 聊天回复完成时,取消输入框的禁止编辑状态 + setState(() { + _inputEnabled.value = true; + }); + } + } + }, + buildWhen: (prv, cur) => cur is GroupChatMessagesLoaded, + builder: (context, state) { + if (state is GroupChatMessagesLoaded) { + final loadedMessages = state.messages + .map((e) => Message( + Role.getRoleFromText(e.role), + e.message, + type: MessageType.text, + status: e.status ?? 1, + refId: e.pid, + ts: e.createdAt, + )) + .toList(); + + final messages = loadedMessages.map((e) { + return MessageWithState( + e, + group.states[ + widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? + MessageState(), + ); + }).toList(); + + _chatPreviewController.setAllMessageIds(messages); + + return ChatPreview( + supportBloc: false, + messages: messages, + scrollController: _scrollController, + controller: _chatPreviewController, + stateManager: widget.stateManager, + robotAvatar: selectMode ? null : _buildAvatar(group.group), + onDeleteMessage: (id) { + handleDeleteMessage(context, id); + }, + onSpeakEvent: (message) { + _audioPlayerController.playAudio(message.text); + }, + onResentEvent: (message) { + _scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), + curve: Curves.easeOut); + _handleSubmit( + message.text, + ); + }, + helpWidgets: state.hasWaitTasks || loadedMessages.isEmpty + ? null + : [ + HelpTips( + onSubmitMessage: _handleSubmit, + onNewChat: () => handleResetContext(context), + ) + ], + ); + } + return const Center(child: CircularProgressIndicator()); + }, + ); + } + + /// 构建 AppBar + AppBar _buildAppBar(BuildContext context, CustomColors customColors) { + return _chatPreviewController.selectMode + ? AppBar( + title: Text( + AppLocale.select.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + centerTitle: true, + elevation: 0, + leading: TextButton( + onPressed: () { + _chatPreviewController.exitSelectMode(); + }, + child: Text( + AppLocale.cancel.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + toolbarHeight: CustomSize.toolbarHeight, + ) + : AppBar( + centerTitle: true, + elevation: 0, + // backgroundColor: customColors.chatRoomBackground, + title: BlocBuilder( + buildWhen: (previous, current) => current is GroupChatLoaded, + builder: (context, state) { + if (state is GroupChatLoaded) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 房间名称 + Text( + state.group.group.name, + style: const TextStyle(fontSize: 16), + ), + ], + ); + } + + return Container(); + }, + ), + actions: [ + buildChatMoreMenu(context, widget.groupId), + ], + toolbarHeight: CustomSize.toolbarHeight, + ); + } + + Widget _buildAvatar(ChatGroup room) { + if (room.group.avatarUrl != null && + room.group.avatarUrl!.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(room.group.avatarUrl!, qiniuImageTypeAvatar), + size: 30, + ); + } + + return RandomAvatar( + id: room.group.id, + size: 30, + usage: + Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + ); + } + + /// 提交新消息 + void _handleSubmit(String text) { + setState(() { + _inputEnabled.value = false; + }); + + context.read().add( + GroupChatSendEvent( + widget.groupId, + text, + selectedMemberIds, + ), + ); + } +} + +/// 处理消息删除事件 +void handleDeleteMessage(BuildContext context, int id, {int? chatHistoryId}) { + openConfirmDialog( + context, + AppLocale.confirmDelete.getString(context), + () {}, + danger: true, + ); +} + +/// 重置上下文 +void handleResetContext(BuildContext context) { + // openConfirmDialog( + // context, + // AppLocale.confirmStartNewChat.getString(context), + // () { + // context.read().add(ChatMessageBreakContextEvent()); + HapticFeedbackHelper.mediumImpact(); + // }, + // ); +} + +/// 清空历史消息 +void handleClearHistory(BuildContext context) { + openConfirmDialog( + context, + AppLocale.confirmClearMessages.getString(context), + () { + // context.read().add(ChatMessageClearAllEvent()); + showSuccessMessage(AppLocale.operateSuccess.getString(context)); + HapticFeedbackHelper.mediumImpact(); + }, + danger: true, + ); +} + +/// 构建聊天内容窗口 +Widget buildSelectModeToolbars( + BuildContext context, + ChatPreviewController chatPreviewController, + CustomColors customColors, +) { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + color: customColors.backgroundColor, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton.icon( + onPressed: () { + var messages = chatPreviewController.selectedMessages(); + if (messages.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; + } + var shareText = messages.map((e) { + if (e.message.role == Role.sender) { + return e.message.text; + } + + return e.message.text; + }).join('\n\n'); + + shareTo( + context, + content: shareText, + title: AppLocale.chatHistory.getString(context), + ); + }, + icon: Icon(Icons.share, color: customColors.linkColor), + label: Text( + AppLocale.share.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + TextButton.icon( + onPressed: () { + chatPreviewController.selectAllMessage(); + }, + icon: Icon(Icons.select_all_outlined, color: customColors.linkColor), + label: Text( + AppLocale.selectAll.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + TextButton.icon( + onPressed: () { + if (chatPreviewController.selectedMessageIds.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; + } + + openConfirmDialog( + context, + AppLocale.confirmDelete.getString(context), + () { + final ids = chatPreviewController.selectedMessageIds.toList(); + if (ids.isNotEmpty) { + // context + // .read() + // .add(ChatMessageDeleteEvent(ids)); + + showErrorMessageEnhanced( + context, AppLocale.operateSuccess.getString(context)); + + chatPreviewController.exitSelectMode(); + } + }, + danger: true, + ); + }, + icon: Icon(Icons.delete, color: customColors.linkColor), + label: Text( + AppLocale.delete.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + ], + ), + ); +} + +/// 构建聊天设置下拉菜单 +Widget buildChatMoreMenu( + BuildContext context, + int chatRoomId, { + bool useLocalContext = true, + bool withSetting = true, +}) { + var customColors = Theme.of(context).extension()!; + + return EnhancedPopupMenu( + items: [ + EnhancedPopupMenuItem( + title: AppLocale.newChat.getString(context), + icon: Icons.post_add, + iconColor: Colors.blue, + onTap: (ctx) { + handleResetContext(useLocalContext ? ctx : context); + }, + ), + EnhancedPopupMenuItem( + title: AppLocale.clearChatHistory.getString(context), + icon: Icons.delete_forever, + iconColor: Colors.red, + onTap: (ctx) { + handleClearHistory(useLocalContext ? ctx : context); + }, + ), + if (withSetting) + EnhancedPopupMenuItem( + title: AppLocale.settings.getString(context), + icon: Icons.settings, + iconColor: customColors.linkColor, + onTap: (_) { + context.push('/room/$chatRoomId/setting'); + }, + ), + ], + ); +} diff --git a/lib/page/quota_usage_statistics.dart b/lib/page/quota_usage_statistics.dart index 758a3c02..1accb901 100644 --- a/lib/page/quota_usage_statistics.dart +++ b/lib/page/quota_usage_statistics.dart @@ -6,6 +6,7 @@ import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; +import 'package:askaide/repo/model/misc.dart'; class QuotaUsageStatisticsScreen extends StatefulWidget { final SettingRepository setting; diff --git a/lib/page/setting_screen.dart b/lib/page/setting_screen.dart index cf7de4a3..4cd907b2 100644 --- a/lib/page/setting_screen.dart +++ b/lib/page/setting_screen.dart @@ -253,6 +253,18 @@ class _SettingScreenState extends State { context.push('/lab/draw-board'); }, ), + + SettingsTile( + title: const Text('群组'), + trailing: Icon( + CupertinoIcons.chevron_forward, + size: MediaQuery.of(context).textScaleFactor * 18, + color: Colors.grey, + ), + onPressed: (context) { + context.push('/group-chat/1001/chat'); + }, + ), // SettingsTile( // title: const Text('用户中心'), // trailing: Icon( diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index fb5e270c..924e787d 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -14,6 +14,8 @@ import 'package:askaide/repo/api/payment.dart'; import 'package:askaide/repo/api/quota.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:askaide/repo/api/user.dart'; +import 'package:askaide/repo/model/group.dart'; +import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; @@ -58,8 +60,8 @@ class APIServer { ]; /// 异常处理 - Object _exceptionHandle(Object e) { - Logger.instance.e(e); + Object _exceptionHandle(Object e, Object? stackTrace) { + Logger.instance.e(e, stackTrace: stackTrace as StackTrace?); if (e is DioError) { if (e.response != null) { @@ -188,6 +190,25 @@ class APIServer { ); } + Future sendPostJSONRequest( + String endpoint, + T Function(dynamic) parser, { + Map? queryParameters, + Map? data, + VoidCallback? finallyCallback, + }) async { + return request( + HttpClient.postJSON( + '$url$endpoint', + queryParameters: queryParameters, + data: data, + options: _buildRequestOptions(), + ), + parser, + finallyCallback: finallyCallback, + ); + } + Future sendPutRequest( String endpoint, T Function(dynamic) parser, { @@ -246,8 +267,8 @@ class APIServer { // Logger.instance.d("API Response: ${resp.data}"); return parser(resp); - } catch (e) { - return Future.error(_exceptionHandle(e)); + } catch (e, stackTrace) { + return Future.error(_exceptionHandle(e, stackTrace)); } finally { finallyCallback?.call(); } @@ -1511,639 +1532,68 @@ class APIServer { }, ); } -} - -enum PromotionEventClickButtonType { - none, - url, - inAppRoute; - - static PromotionEventClickButtonType fromName(String typeName) { - switch (typeName) { - case 'url': - return PromotionEventClickButtonType.url; - case 'in_app_route': - return PromotionEventClickButtonType.inAppRoute; - default: - return PromotionEventClickButtonType.none; - } - } - - String toName() { - switch (this) { - case PromotionEventClickButtonType.url: - return 'url'; - case PromotionEventClickButtonType.inAppRoute: - return 'in_app_route'; - default: - return 'none'; - } - } -} - -class PromotionEvent { - String? title; - String content; - PromotionEventClickButtonType clickButtonType; - String? clickValue; - String? clickButtonColor; - String? backgroundImage; - String? textColor; - bool closeable; - int? maxCloseDurationInDays; - - PromotionEvent({ - this.title, - required this.content, - required this.clickButtonType, - this.clickValue, - this.clickButtonColor, - this.backgroundImage, - this.textColor, - required this.closeable, - this.maxCloseDurationInDays, - }); - - toJson() => { - 'title': title, - 'content': content, - 'click_button_type': clickButtonType.toName(), - 'click_value': clickValue, - 'click_button_color': clickButtonColor, - 'background_image': backgroundImage, - 'text_color': textColor, - 'closeable': closeable, - 'max_close_duration_in_days': maxCloseDurationInDays, - }; - - static PromotionEvent fromJson(Map json) { - return PromotionEvent( - title: json['title'], - content: json['content'], - clickButtonType: PromotionEventClickButtonType.fromName( - json['click_button_type'] ?? ''), - clickValue: json['click_value'], - clickButtonColor: json['click_button_color'], - backgroundImage: json['background_image'], - textColor: json['text_color'], - closeable: json['closeable'] ?? false, - maxCloseDurationInDays: json['max_close_duration_in_days'], - ); - } -} - -class ShareInfo { - String qrCode; - String message; - String? inviteCode; - - ShareInfo({ - required this.qrCode, - required this.message, - this.inviteCode, - }); - - toJson() => { - 'qr_code': qrCode, - 'message': message, - 'invite_code': inviteCode, - }; - - static ShareInfo fromJson(Map json) { - return ShareInfo( - qrCode: json['qr_code'], - message: json['message'], - inviteCode: json['invite_code'], - ); - } -} - -class QuotaUsageInDay { - String date; - int used; - - QuotaUsageInDay({ - required this.date, - required this.used, - }); - - toJson() => { - 'date': date, - 'used': used, - }; - - static QuotaUsageInDay fromJson(Map json) { - return QuotaUsageInDay( - date: json['date'], - used: json['used'], - ); - } -} - -class RoomsResponse { - List rooms; - List? suggests; - - RoomsResponse({ - required this.rooms, - this.suggests, - }); - - toJson() => { - 'rooms': rooms, - 'suggests': suggests, - }; - - static RoomsResponse fromJson(Map json) { - var rooms = []; - for (var item in json['data'] ?? []) { - rooms.add(RoomInServer.fromJson(item)); - } - - var suggests = []; - for (var item in json['suggests'] ?? []) { - suggests.add(RoomGallery.fromJson(item)); - } - - return RoomsResponse( - rooms: rooms, - suggests: suggests, - ); - } -} - -class RoomInServer { - int id; - int userId; - int avatarId; - String? avatarUrl; - String name; - String? description; - int? priority; - String model; - String vendor; - String? systemPrompt; - String? initMessage; - int maxContext; - int? maxTokens; - DateTime? lastActiveTime; - DateTime? createdAt; - DateTime? updatedAt; - - RoomInServer({ - required this.id, - required this.userId, - required this.avatarId, - required this.name, - required this.maxContext, - this.avatarUrl, - this.description, - this.priority, - required this.model, - required this.vendor, - this.systemPrompt, - this.initMessage, - this.lastActiveTime, - this.createdAt, - this.updatedAt, - this.maxTokens, - }); - - toJson() => { - 'id': id, - 'user_id': userId, - 'avatar_id': avatarId, - 'avatar_url': avatarUrl, - 'name': name, - 'description': description, - 'priority': priority, - 'model': model, - 'vendor': vendor, - 'init_message': initMessage, - 'max_context': maxContext, - 'max_tokens': maxTokens, - 'system_prompt': systemPrompt, - 'last_active_time': lastActiveTime?.toIso8601String(), - 'created_at': createdAt?.toIso8601String(), - 'updated_at': updatedAt?.toIso8601String(), - }; - - static RoomInServer fromJson(Map json) { - return RoomInServer( - id: json['id'], - userId: json['user_id'], - avatarId: json['avatar_id'] ?? 0, - avatarUrl: json['avatar_url'], - name: json['name'], - description: json['description'], - priority: json['priority'], - model: json['model'], - vendor: json['vendor'], - systemPrompt: json['system_prompt'], - initMessage: json['init_message'], - maxContext: json['max_context'] ?? 10, - maxTokens: json['max_tokens'], - lastActiveTime: json['last_active_time'] != null - ? DateTime.parse(json['last_active_time']) - : null, - createdAt: - json['CreatedAt'] != null ? DateTime.parse(json['CreatedAt']) : null, - updatedAt: - json['UpdatedAt'] != null ? DateTime.parse(json['UpdatedAt']) : null, - ); - } -} - -class VersionCheckResp { - bool hasUpdate; - String serverVersion; - bool forceUpdate; - String url; - String message; - - VersionCheckResp({ - required this.hasUpdate, - required this.serverVersion, - required this.forceUpdate, - required this.url, - required this.message, - }); - - toJson() => { - 'has_update': hasUpdate, - 'server_version': serverVersion, - 'force_update': forceUpdate, - 'url': url, - 'message': message, - }; - - static VersionCheckResp fromJson(Map json) { - return VersionCheckResp( - hasUpdate: json['has_update'] ?? false, - serverVersion: json['server_version'], - forceUpdate: json['force_update'] ?? false, - url: json['url'], - message: json['message'], - ); - } -} - -class SignInResp { - int id; - String name; - String? email; - String? phone; - String token; - bool isNewUser; - int reward; - - SignInResp({ - required this.id, - required this.name, - this.email, - required this.token, - this.phone, - this.isNewUser = false, - this.reward = 0, - }); - - toJson() => { - 'id': id, - 'name': name, - 'email': email, - 'phone': phone, - 'token': token, - 'is_new_user': isNewUser, - 'reward': reward, - }; - - bool get needBindPhone => phone == null || phone!.isEmpty; - - static SignInResp fromJson(Map json) { - return SignInResp( - id: json['id'], - name: json['name'], - email: json['email'], - phone: json['phone'], - token: json['token'], - isNewUser: json['is_new_user'] ?? false, - reward: json['reward'] ?? 0, - ); - } -} - -class AsyncTaskResp { - String status; - List? errors; - List? resources; - String? originImage; - - AsyncTaskResp(this.status, {this.errors, this.resources, this.originImage}); - - toJson() => { - 'status': status, - 'errors': errors, - 'resources': resources, - 'origin_image': originImage, - }; - - static AsyncTaskResp fromJson(Map json) { - return AsyncTaskResp( - json['status'], - errors: json['errors'] != null - ? (json['errors'] as List).map((e) => e.toString()).toList() - : null, - resources: json['resources'] != null - ? (json['resources'] as List) - .map((e) => e.toString()) - .toList() - : null, - originImage: json['origin_image'], - ); - } -} - -class Prompt { - String title; - String content; - - Prompt(this.title, this.content); - - toJson() { - return { - 'title': title, - 'content': content, - }; - } - - fromJson(Map json) { - title = json['title']; - content = json['content']; - } -} - -class ChatExample { - String title; - String? content; - List models; - List tags; - - ChatExample( - this.title, { - this.content, - this.models = const [], - this.tags = const [], - }); - - get text => content ?? title; - - toJson() => { - 'title': title, - 'content': content, - 'models': models, - 'tags': tags, - }; - - fromJson(Map json) { - title = json['title']; - content = json['content']; - models = json['models']; - tags = json['tags']; - } -} - -class TranslateText { - String? result; - String? speakUrl; - - TranslateText(this.result, this.speakUrl); - - toJson() => { - 'result': result, - 'speak_url': speakUrl, - }; - - static fromJson(Map json) { - return TranslateText(json['result'], json['speak_url']); - } -} - -class UploadInitResponse { - String bucket; - String key; - String token; - String url; - - UploadInitResponse(this.key, this.bucket, this.token, this.url); - - toJson() => { - 'bucket': bucket, - 'key': key, - 'token': token, - 'url': url, - }; - - static fromJson(Map json) { - return UploadInitResponse( - json['key'], - json['bucket'], - json['token'], - json['url'], - ); - } -} - -class ModelStyle { - String id; - String name; - String? preview; - ModelStyle({required this.id, required this.name, this.preview}); + /// 群聊 //////////////////////////////////////////////////////////////////// - toJson() => { - 'id': id, - 'name': name, - 'preview': preview, - }; - - static ModelStyle fromJson(Map json) { - return ModelStyle( - id: json['id'], - name: json['name'], - preview: json['preview'], - ); - } -} - -class Model { - String id; - String name; - String shortName; - String? description; - String category; - bool isChat; - bool isImage; - bool disabled; - String? tag; - - Model({ - required this.id, - required this.name, - required this.shortName, - required this.category, - required this.isChat, - required this.isImage, - this.description, - this.disabled = false, - this.tag, - }); - - toJson() => { - 'id': id, - 'name': name, - 'short_name': shortName, - 'description': description, - 'category': category, - 'is_chat': isChat, - 'is_image': isImage, - 'disabled': disabled, - 'tag': tag, - }; - - static Model fromJson(Map json) { - return Model( - id: json['id'], - name: json['name'], - shortName: json['short_name'] ?? json['name'], - description: json['description'], - category: json['category'], - isChat: json['is_chat'], - isImage: json['is_image'], - disabled: json['disabled'] ?? false, - tag: json['tag'], - ); - } -} - -class BackgroundImage { - String url; - String preview; - - BackgroundImage(this.url, this.preview); - - toJson() => { - 'url': url, - 'preview': preview, - }; - - static BackgroundImage fromJson(Map json) { - return BackgroundImage( - json['url'], - json['preview'], - ); - } -} - -class UserExistenceResp { - bool exist; - String signInMethod; - - UserExistenceResp(this.exist, this.signInMethod); - - toJson() => { - 'exist': exist, - 'sign_in_method': signInMethod, - }; + /// 群组列表 + Future> chatGroups({bool cache = true}) async { + return sendCachedGetRequest( + '/v1/group-chat', + (value) { + var res = []; + for (var item in value.data['data']) { + res.add(RoomInServer.fromJson(item)); + } - static UserExistenceResp fromJson(Map json) { - return UserExistenceResp( - json['exist'], - json['sign_in_method'], + return res; + }, + forceRefresh: !cache, ); } -} -class PromptCategory { - String name; - List children; - List tags; - - PromptCategory(this.name, this.children, this.tags); - - toJson() => { - 'name': name, - 'children': children, - 'tags': tags, - }; - - static PromptCategory fromJson(Map json) { - var children = []; - for (var item in json['children'] ?? []) { - children.add(PromptCategory.fromJson(item)); - } - - var tags = []; - for (var item in json['tags'] ?? []) { - tags.add(PromptTag.fromJson(item)); - } - - return PromptCategory( - json['name'], - children, - tags, + /// 群组详情 + Future chatGroup(int groupId, {bool cache = true}) async { + return sendCachedGetRequest( + '/v1/group-chat/$groupId', + (value) => ChatGroup.fromJson(value.data), + forceRefresh: !cache, ); } -} -class PromptTag { - String name; - String value; - - PromptTag(this.name, this.value); - - toJson() => { - 'name': name, - 'value': value, - }; + /// 群组聊天消息列表 + Future> chatGroupMessages( + int groupId, { + int page = 1, + int perPage = 100, + }) async { + return sendGetRequest( + '/v1/group-chat/$groupId/messages', + (resp) { + var res = []; + for (var item in resp.data['data']) { + res.add(GroupMessage.fromJson(item)); + } - static PromptTag fromJson(Map json) { - return PromptTag( - json['name'], - json['value'], + return PagedData( + data: res, + page: resp.data['page'] ?? 1, + perPage: resp.data['per_page'] ?? 100, + total: resp.data['total'], + lastPage: resp.data['last_page'], + ); + }, ); } -} -class FreeModelCount { - String model; - String name; - int leftCount; - int maxCount; - String? info; - - FreeModelCount({ - required this.model, - required this.name, - required this.leftCount, - required this.maxCount, - this.info, - }); - - toJson() => { - 'model': model, - 'name': name, - 'left_count': leftCount, - 'max_count': maxCount, - 'info': info, - }; - - static FreeModelCount fromJson(Map json) { - return FreeModelCount( - model: json['model'], - name: json['name'] ?? json['model'], - leftCount: json['left_count'] ?? 0, - maxCount: json['max_count'] ?? 0, - info: json['info'], + /// 发起群聊消息 + Future chatGroupSendMessage( + int groupId, GroupChatSendRequest req) async { + return sendPostJSONRequest( + '/v1/group-chat/$groupId/chat', + (resp) { + return GroupChatSendResponse.fromJson(resp.data); + }, + data: req.toJson(), ); } } diff --git a/lib/repo/model/group.dart b/lib/repo/model/group.dart new file mode 100644 index 00000000..102734a4 --- /dev/null +++ b/lib/repo/model/group.dart @@ -0,0 +1,227 @@ +import 'package:askaide/repo/model/misc.dart'; + +const groupMessageStatusWaiting = 0; +const groupMessageStatusSuccess = 1; +const groupMessageStatusFailed = 2; + +class ChatGroup { + final RoomInServer group; + final List members; + + ChatGroup({ + required this.group, + required this.members, + }); + + factory ChatGroup.fromJson(Map json) { + return ChatGroup( + group: RoomInServer.fromJson(json['group']), + members: (json['members'] as List) + .map((member) => GroupMember.fromJson(member)) + .toList(), + ); + } + + Map toJson() { + return { + 'group': group.toJson(), + 'members': members.map((member) => member.toJson()).toList(), + }; + } +} + +class GroupMember { + final int? id; + final String modelId; + final String modelName; + final String? avatarUrl; + + GroupMember({ + this.id, + required this.modelId, + required this.modelName, + this.avatarUrl, + }); + + factory GroupMember.fromJson(Map json) { + return GroupMember( + id: json['id'], + modelId: json['model_id'], + modelName: json['model_name'], + avatarUrl: json['avatar_url'], + ); + } + + Map toJson() { + return { + 'id': id, + 'model_id': modelId, + 'model_name': modelName, + 'avatar_url': avatarUrl, + }; + } +} + +class GroupMessage { + final int id; + final String message; + final String role; + final int? tokenConsumed; + final int? quotaConsumed; + final int? pid; + final int? memberId; + final int? status; + DateTime? createdAt; + DateTime? updatedAt; + + GroupMessage({ + required this.id, + required this.message, + required this.role, + this.tokenConsumed, + this.quotaConsumed, + this.pid, + this.memberId, + this.status, + this.createdAt, + this.updatedAt, + }); + + factory GroupMessage.fromJson(Map json) { + return GroupMessage( + id: json['id'], + message: json['message'] ?? '', + role: json['role'] == 1 ? 'user' : 'assistant', + tokenConsumed: json['token_consumed'], + quotaConsumed: json['quota_consumed'], + pid: json['pid'], + memberId: json['member_id'], + status: json['status'], + createdAt: DateTime.tryParse(json['CreatedAt']), + updatedAt: DateTime.tryParse(json['UpdatedAt']), + ); + } + + Map toJson() { + return { + 'id': id, + 'message': message, + 'role': role, + 'token_consumed': tokenConsumed, + 'quota_consumed': quotaConsumed, + 'pid': pid, + 'member_id': memberId, + 'status': status, + 'created_at': createdAt?.toIso8601String(), + 'updated_at': updatedAt?.toIso8601String(), + }; + } +} + +class GroupChatSendRequestMessage { + final String role; + final String content; + final int? memberId; + + GroupChatSendRequestMessage({ + required this.role, + required this.content, + this.memberId, + }); + + Map toJson() { + return { + 'role': role, + 'content': content, + 'member_id': memberId, + }; + } + + factory GroupChatSendRequestMessage.fromJson(Map json) { + return GroupChatSendRequestMessage( + role: json['role'], + content: json['content'], + memberId: json['member_id'], + ); + } +} + +class GroupChatSendRequest { + final List messages; + final List memberIds; + + GroupChatSendRequest({ + required this.messages, + required this.memberIds, + }); + + Map toJson() { + return { + 'messages': messages.map((message) => message.toJson()).toList(), + 'member_ids': memberIds, + }; + } + + factory GroupChatSendRequest.fromJson(Map json) { + return GroupChatSendRequest( + messages: ((json['messages'] ?? []) as List) + .map((message) => GroupChatSendRequestMessage.fromJson(message)) + .toList(), + memberIds: (json['member_ids'] as List).map((e) => e as int).toList(), + ); + } +} + +class GroupChatSendResponseTask { + final int memberId; + final String taskId; + final int answerId; + + GroupChatSendResponseTask({ + required this.memberId, + required this.taskId, + required this.answerId, + }); + + factory GroupChatSendResponseTask.fromJson(Map json) { + return GroupChatSendResponseTask( + memberId: json['member_id'], + taskId: json['task_id'], + answerId: json['answer_id'], + ); + } + + Map toJson() { + return { + 'member_id': memberId, + 'task_id': taskId, + 'answer_id': answerId, + }; + } +} + +class GroupChatSendResponse { + final int questionId; + final List tasks; + + GroupChatSendResponse({ + required this.questionId, + required this.tasks, + }); + + factory GroupChatSendResponse.fromJson(Map json) { + return GroupChatSendResponse( + questionId: json['question_id'], + tasks: (json['tasks'] as List) + .map((task) => GroupChatSendResponseTask.fromJson(task)) + .toList(), + ); + } + + Map toJson() { + return { + 'question_id': questionId, + 'tasks': tasks.map((task) => task.toJson()).toList(), + }; + } +} diff --git a/lib/repo/model/message.dart b/lib/repo/model/message.dart index 00f8fb87..68f3a4d3 100644 --- a/lib/repo/model/message.dart +++ b/lib/repo/model/message.dart @@ -175,8 +175,12 @@ enum Role { switch (value) { case 'receiver': return Role.receiver; + case 'assistant': + return Role.receiver; case 'sender': return Role.sender; + case 'user': + return Role.sender; default: return Role.receiver; } diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart new file mode 100644 index 00000000..504bd193 --- /dev/null +++ b/lib/repo/model/misc.dart @@ -0,0 +1,636 @@ +import 'package:askaide/repo/api/room_gallery.dart'; + +enum PromotionEventClickButtonType { + none, + url, + inAppRoute; + + static PromotionEventClickButtonType fromName(String typeName) { + switch (typeName) { + case 'url': + return PromotionEventClickButtonType.url; + case 'in_app_route': + return PromotionEventClickButtonType.inAppRoute; + default: + return PromotionEventClickButtonType.none; + } + } + + String toName() { + switch (this) { + case PromotionEventClickButtonType.url: + return 'url'; + case PromotionEventClickButtonType.inAppRoute: + return 'in_app_route'; + default: + return 'none'; + } + } +} + +class PromotionEvent { + String? title; + String content; + PromotionEventClickButtonType clickButtonType; + String? clickValue; + String? clickButtonColor; + String? backgroundImage; + String? textColor; + bool closeable; + int? maxCloseDurationInDays; + + PromotionEvent({ + this.title, + required this.content, + required this.clickButtonType, + this.clickValue, + this.clickButtonColor, + this.backgroundImage, + this.textColor, + required this.closeable, + this.maxCloseDurationInDays, + }); + + toJson() => { + 'title': title, + 'content': content, + 'click_button_type': clickButtonType.toName(), + 'click_value': clickValue, + 'click_button_color': clickButtonColor, + 'background_image': backgroundImage, + 'text_color': textColor, + 'closeable': closeable, + 'max_close_duration_in_days': maxCloseDurationInDays, + }; + + static PromotionEvent fromJson(Map json) { + return PromotionEvent( + title: json['title'], + content: json['content'], + clickButtonType: PromotionEventClickButtonType.fromName( + json['click_button_type'] ?? ''), + clickValue: json['click_value'], + clickButtonColor: json['click_button_color'], + backgroundImage: json['background_image'], + textColor: json['text_color'], + closeable: json['closeable'] ?? false, + maxCloseDurationInDays: json['max_close_duration_in_days'], + ); + } +} + +class ShareInfo { + String qrCode; + String message; + String? inviteCode; + + ShareInfo({ + required this.qrCode, + required this.message, + this.inviteCode, + }); + + toJson() => { + 'qr_code': qrCode, + 'message': message, + 'invite_code': inviteCode, + }; + + static ShareInfo fromJson(Map json) { + return ShareInfo( + qrCode: json['qr_code'], + message: json['message'], + inviteCode: json['invite_code'], + ); + } +} + +class QuotaUsageInDay { + String date; + int used; + + QuotaUsageInDay({ + required this.date, + required this.used, + }); + + toJson() => { + 'date': date, + 'used': used, + }; + + static QuotaUsageInDay fromJson(Map json) { + return QuotaUsageInDay( + date: json['date'], + used: json['used'], + ); + } +} + +class RoomsResponse { + List rooms; + List? suggests; + + RoomsResponse({ + required this.rooms, + this.suggests, + }); + + toJson() => { + 'rooms': rooms, + 'suggests': suggests, + }; + + static RoomsResponse fromJson(Map json) { + var rooms = []; + for (var item in json['data'] ?? []) { + rooms.add(RoomInServer.fromJson(item)); + } + + var suggests = []; + for (var item in json['suggests'] ?? []) { + suggests.add(RoomGallery.fromJson(item)); + } + + return RoomsResponse( + rooms: rooms, + suggests: suggests, + ); + } +} + +class RoomInServer { + int id; + int userId; + int avatarId; + String? avatarUrl; + String name; + String? description; + int? priority; + String model; + String vendor; + String? systemPrompt; + String? initMessage; + int maxContext; + int? maxTokens; + DateTime? lastActiveTime; + DateTime? createdAt; + DateTime? updatedAt; + + RoomInServer({ + required this.id, + required this.userId, + required this.avatarId, + required this.name, + required this.maxContext, + this.avatarUrl, + this.description, + this.priority, + required this.model, + required this.vendor, + this.systemPrompt, + this.initMessage, + this.lastActiveTime, + this.createdAt, + this.updatedAt, + this.maxTokens, + }); + + toJson() => { + 'id': id, + 'user_id': userId, + 'avatar_id': avatarId, + 'avatar_url': avatarUrl, + 'name': name, + 'description': description, + 'priority': priority, + 'model': model, + 'vendor': vendor, + 'init_message': initMessage, + 'max_context': maxContext, + 'max_tokens': maxTokens, + 'system_prompt': systemPrompt, + 'last_active_time': lastActiveTime?.toIso8601String(), + 'created_at': createdAt?.toIso8601String(), + 'updated_at': updatedAt?.toIso8601String(), + }; + + static RoomInServer fromJson(Map json) { + return RoomInServer( + id: json['id'], + userId: json['user_id'], + avatarId: json['avatar_id'] ?? 0, + avatarUrl: json['avatar_url'], + name: json['name'], + description: json['description'], + priority: json['priority'], + model: json['model'] ?? '', + vendor: json['vendor'] ?? '', + systemPrompt: json['system_prompt'], + initMessage: json['init_message'], + maxContext: json['max_context'] ?? 10, + maxTokens: json['max_tokens'], + lastActiveTime: json['last_active_time'] != null + ? DateTime.parse(json['last_active_time']) + : null, + createdAt: + json['CreatedAt'] != null ? DateTime.parse(json['CreatedAt']) : null, + updatedAt: + json['UpdatedAt'] != null ? DateTime.parse(json['UpdatedAt']) : null, + ); + } +} + +class VersionCheckResp { + bool hasUpdate; + String serverVersion; + bool forceUpdate; + String url; + String message; + + VersionCheckResp({ + required this.hasUpdate, + required this.serverVersion, + required this.forceUpdate, + required this.url, + required this.message, + }); + + toJson() => { + 'has_update': hasUpdate, + 'server_version': serverVersion, + 'force_update': forceUpdate, + 'url': url, + 'message': message, + }; + + static VersionCheckResp fromJson(Map json) { + return VersionCheckResp( + hasUpdate: json['has_update'] ?? false, + serverVersion: json['server_version'], + forceUpdate: json['force_update'] ?? false, + url: json['url'], + message: json['message'], + ); + } +} + +class SignInResp { + int id; + String name; + String? email; + String? phone; + String token; + bool isNewUser; + int reward; + + SignInResp({ + required this.id, + required this.name, + this.email, + required this.token, + this.phone, + this.isNewUser = false, + this.reward = 0, + }); + + toJson() => { + 'id': id, + 'name': name, + 'email': email, + 'phone': phone, + 'token': token, + 'is_new_user': isNewUser, + 'reward': reward, + }; + + bool get needBindPhone => phone == null || phone!.isEmpty; + + static SignInResp fromJson(Map json) { + return SignInResp( + id: json['id'], + name: json['name'], + email: json['email'], + phone: json['phone'], + token: json['token'], + isNewUser: json['is_new_user'] ?? false, + reward: json['reward'] ?? 0, + ); + } +} + +class AsyncTaskResp { + String status; + List? errors; + List? resources; + String? originImage; + + AsyncTaskResp(this.status, {this.errors, this.resources, this.originImage}); + + toJson() => { + 'status': status, + 'errors': errors, + 'resources': resources, + 'origin_image': originImage, + }; + + static AsyncTaskResp fromJson(Map json) { + return AsyncTaskResp( + json['status'], + errors: json['errors'] != null + ? (json['errors'] as List).map((e) => e.toString()).toList() + : null, + resources: json['resources'] != null + ? (json['resources'] as List) + .map((e) => e.toString()) + .toList() + : null, + originImage: json['origin_image'], + ); + } +} + +class Prompt { + String title; + String content; + + Prompt(this.title, this.content); + + toJson() { + return { + 'title': title, + 'content': content, + }; + } + + fromJson(Map json) { + title = json['title']; + content = json['content']; + } +} + +class ChatExample { + String title; + String? content; + List models; + List tags; + + ChatExample( + this.title, { + this.content, + this.models = const [], + this.tags = const [], + }); + + get text => content ?? title; + + toJson() => { + 'title': title, + 'content': content, + 'models': models, + 'tags': tags, + }; + + fromJson(Map json) { + title = json['title']; + content = json['content']; + models = json['models']; + tags = json['tags']; + } +} + +class TranslateText { + String? result; + String? speakUrl; + + TranslateText(this.result, this.speakUrl); + + toJson() => { + 'result': result, + 'speak_url': speakUrl, + }; + + static fromJson(Map json) { + return TranslateText(json['result'], json['speak_url']); + } +} + +class UploadInitResponse { + String bucket; + String key; + String token; + String url; + + UploadInitResponse(this.key, this.bucket, this.token, this.url); + + toJson() => { + 'bucket': bucket, + 'key': key, + 'token': token, + 'url': url, + }; + + static fromJson(Map json) { + return UploadInitResponse( + json['key'], + json['bucket'], + json['token'], + json['url'], + ); + } +} + +class ModelStyle { + String id; + String name; + String? preview; + + ModelStyle({required this.id, required this.name, this.preview}); + + toJson() => { + 'id': id, + 'name': name, + 'preview': preview, + }; + + static ModelStyle fromJson(Map json) { + return ModelStyle( + id: json['id'], + name: json['name'], + preview: json['preview'], + ); + } +} + +class Model { + String id; + String name; + String shortName; + String? description; + String category; + bool isChat; + bool isImage; + bool disabled; + String? tag; + + Model({ + required this.id, + required this.name, + required this.shortName, + required this.category, + required this.isChat, + required this.isImage, + this.description, + this.disabled = false, + this.tag, + }); + + toJson() => { + 'id': id, + 'name': name, + 'short_name': shortName, + 'description': description, + 'category': category, + 'is_chat': isChat, + 'is_image': isImage, + 'disabled': disabled, + 'tag': tag, + }; + + static Model fromJson(Map json) { + return Model( + id: json['id'], + name: json['name'], + shortName: json['short_name'] ?? json['name'], + description: json['description'], + category: json['category'], + isChat: json['is_chat'], + isImage: json['is_image'], + disabled: json['disabled'] ?? false, + tag: json['tag'], + ); + } +} + +class BackgroundImage { + String url; + String preview; + + BackgroundImage(this.url, this.preview); + + toJson() => { + 'url': url, + 'preview': preview, + }; + + static BackgroundImage fromJson(Map json) { + return BackgroundImage( + json['url'], + json['preview'], + ); + } +} + +class UserExistenceResp { + bool exist; + String signInMethod; + + UserExistenceResp(this.exist, this.signInMethod); + + toJson() => { + 'exist': exist, + 'sign_in_method': signInMethod, + }; + + static UserExistenceResp fromJson(Map json) { + return UserExistenceResp( + json['exist'], + json['sign_in_method'], + ); + } +} + +class PromptCategory { + String name; + List children; + List tags; + + PromptCategory(this.name, this.children, this.tags); + + toJson() => { + 'name': name, + 'children': children, + 'tags': tags, + }; + + static PromptCategory fromJson(Map json) { + var children = []; + for (var item in json['children'] ?? []) { + children.add(PromptCategory.fromJson(item)); + } + + var tags = []; + for (var item in json['tags'] ?? []) { + tags.add(PromptTag.fromJson(item)); + } + + return PromptCategory( + json['name'], + children, + tags, + ); + } +} + +class PromptTag { + String name; + String value; + + PromptTag(this.name, this.value); + + toJson() => { + 'name': name, + 'value': value, + }; + + static PromptTag fromJson(Map json) { + return PromptTag( + json['name'], + json['value'], + ); + } +} + +class FreeModelCount { + String model; + String name; + int leftCount; + int maxCount; + String? info; + + FreeModelCount({ + required this.model, + required this.name, + required this.leftCount, + required this.maxCount, + this.info, + }); + + toJson() => { + 'model': model, + 'name': name, + 'left_count': leftCount, + 'max_count': maxCount, + 'info': info, + }; + + static FreeModelCount fromJson(Map json) { + return FreeModelCount( + model: json['model'], + name: json['name'] ?? json['model'], + leftCount: json['left_count'] ?? 0, + maxCount: json['max_count'] ?? 0, + info: json['info'], + ); + } +} diff --git a/lib/repo/model/room.dart b/lib/repo/model/room.dart index a1973925..c4f4eb31 100644 --- a/lib/repo/model/room.dart +++ b/lib/repo/model/room.dart @@ -124,9 +124,9 @@ class Room { avatarId = map['avatar_id'] as int?, avatarUrl = map['avatar_url'] as String?, name = map['name'] as String, - category = map['category'] as String, - priority = map['priority'] as int, - model = map['model'] as String, + category = (map['category'] ?? '') as String, + priority = (map['priority'] ?? 0) as int, + model = (map['model'] ?? '') as String, iconData = map['icon_data'] as String?, color = map['color'] as String?, systemPrompt = map['system_prompt'] as String?, From 8f4e2d11ef1a9d8f64610a89fb11a7c682ab4150 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 19 Oct 2023 17:55:04 +0800 Subject: [PATCH 02/28] update --- lib/bloc/group_chat_bloc.dart | 100 +++- lib/bloc/group_chat_event.dart | 30 +- lib/bloc/group_chat_state.dart | 8 +- lib/bloc/room_bloc.dart | 4 + lib/page/component/chat/chat_input.dart | 4 + lib/page/component/chat/chat_preview.dart | 36 +- lib/page/component/multi_item_selector.dart | 129 +++++ lib/page/group/chat.dart | 509 +++++++++++++------- lib/page/rooms.dart | 5 +- lib/page/setting_screen.dart | 2 +- lib/repo/api_server.dart | 58 ++- lib/repo/model/group.dart | 24 +- lib/repo/model/message.dart | 8 + lib/repo/model/misc.dart | 4 + lib/repo/model/room.dart | 6 + 15 files changed, 706 insertions(+), 221 deletions(-) create mode 100644 lib/page/component/multi_item_selector.dart diff --git a/lib/bloc/group_chat_bloc.dart b/lib/bloc/group_chat_bloc.dart index efc69749..35291048 100644 --- a/lib/bloc/group_chat_bloc.dart +++ b/lib/bloc/group_chat_bloc.dart @@ -1,6 +1,8 @@ +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'; @@ -21,37 +23,93 @@ class GroupChatBloc extends Bloc { // 加载聊天组聊天记录 on((event, emit) async { - await refreshGroupMessages( - event.groupId, - page: event.page, - perPage: event.perPage, - ); + await refreshGroupMessages(event.groupId, page: event.page); emit(GroupChatMessagesLoaded(messages: messages)); }); // 发送聊天组消息 on((event, emit) async { - final List requestMessages = [ - GroupChatSendRequestMessage(role: "user", content: event.message) - ]; - final resp = await APIServer().chatGroupSendMessage( - event.groupId, - GroupChatSendRequest( - messages: requestMessages, memberIds: event.members), - ); - - await refreshGroupMessages(event.groupId, page: 1, perPage: 100); + try { + final resp = await APIServer().chatGroupSendMessage( + event.groupId, + GroupChatSendRequest( + message: event.message, + memberIds: event.members, + ), + ); + + Logger.instance.d(resp.toJson()); + + await refreshGroupMessages(event.groupId, page: 1); + emit(GroupChatMessagesLoaded(messages: messages)); + } catch (e) { + await refreshGroupMessages(event.groupId, page: 1); + emit(GroupChatMessagesLoaded(messages: messages, error: e)); + } + }); + + // 发送系统消息 + on((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, page: 1); + emit(GroupChatMessagesLoaded(messages: messages)); + } + }); + + // 更新聊天组消息状态 + on((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 = {}; + 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((event, emit) async { + await APIServer().chatGroupDeleteAllMessages(event.groupId); + messages.clear(); + emit(GroupChatMessagesLoaded(messages: messages)); + }); + + // 删除聊天组消息 + on((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 page = 1, int perPage = 100}) async { - final data = await APIServer().chatGroupMessages( - groupId, - page: page, - perPage: perPage, - ); + refreshGroupMessages(int groupId, {int page = 1}) async { + final data = await APIServer().chatGroupMessages(groupId, page: page); messages = data.data.reversed.toList(); } diff --git a/lib/bloc/group_chat_event.dart b/lib/bloc/group_chat_event.dart index 7ae0e578..5387e174 100644 --- a/lib/bloc/group_chat_event.dart +++ b/lib/bloc/group_chat_event.dart @@ -12,9 +12,8 @@ class GroupChatLoadEvent extends GroupChatEvent { class GroupChatMessagesLoadEvent extends GroupChatEvent { final int groupId; final int page; - final int perPage; - GroupChatMessagesLoadEvent(this.groupId, {this.page = 1, this.perPage = 100}); + GroupChatMessagesLoadEvent(this.groupId, {this.page = 1}); } class GroupChatSendEvent extends GroupChatEvent { @@ -24,3 +23,30 @@ class GroupChatSendEvent extends GroupChatEvent { GroupChatSendEvent(this.groupId, this.message, this.members); } + +class GroupChatUpdateMessageStatusEvent extends GroupChatEvent { + final int groupId; + + GroupChatUpdateMessageStatusEvent(this.groupId); +} + +class GroupChatSendSystemEvent extends GroupChatEvent { + final int groupId; + final String? message; + final MessageType type; + + GroupChatSendSystemEvent(this.groupId, this.type, {this.message}); +} + +class GroupChatDeleteAllEvent extends GroupChatEvent { + final int groupId; + + GroupChatDeleteAllEvent(this.groupId); +} + +class GroupChatDeleteEvent extends GroupChatEvent { + final int groupId; + final int messageId; + + GroupChatDeleteEvent(this.groupId, this.messageId); +} diff --git a/lib/bloc/group_chat_state.dart b/lib/bloc/group_chat_state.dart index 3d9b959a..f24969c2 100644 --- a/lib/bloc/group_chat_state.dart +++ b/lib/bloc/group_chat_state.dart @@ -14,9 +14,15 @@ class GroupChatLoaded extends GroupChatState { class GroupChatMessagesLoaded extends GroupChatState { final List messages; + final Object? _error; + + get error => _error; bool get hasWaitTasks => messages.any((element) => element.status == groupMessageStatusWaiting); - GroupChatMessagesLoaded({required this.messages}); + GroupChatMessagesLoaded({ + required this.messages, + Object? error, + }) : _error = error; } diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index 1c95b0f4..86654cae 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -62,6 +62,7 @@ class RoomBloc extends BlocExt { maxContext: room.maxContext, avatarId: room.avatarId, avatarUrl: room.avatarUrl, + roomType: room.roomType, ), const {}, cascading: false, @@ -84,6 +85,7 @@ class RoomBloc extends BlocExt { maxContext: room.maxContext, avatarId: room.avatarId, avatarUrl: room.avatarUrl, + roomType: room.roomType, ), states, examples: await APIServer().example('${room.vendor}:${room.model}'), @@ -205,6 +207,7 @@ class RoomBloc extends BlocExt { avatarId: room.avatarId, avatarUrl: room.avatarUrl, initMessage: room.initMessage, + roomType: room.roomType, ), states, examples: await APIServer().example(room.model), @@ -285,6 +288,7 @@ class RoomBloc extends BlocExt { model: '${room.vendor}:${room.model}', avatarId: room.avatarId, avatarUrl: room.avatarUrl, + roomType: room.roomType, )) .toList(), suggests: resp.suggests ?? [], diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index b3f2280d..e950b148 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -25,6 +25,7 @@ class ChatInput extends StatefulWidget { final Function()? onNewChat; final String hintText; final Function()? onVoiceRecordTappedEvent; + final List Function()? leftSideToolsBuilder; const ChatInput({ super.key, @@ -35,6 +36,7 @@ class ChatInput extends StatefulWidget { this.onNewChat, this.hintText = '', this.onVoiceRecordTappedEvent, + this.leftSideToolsBuilder, }); @override @@ -157,6 +159,8 @@ class _ChatInputState extends State { Ability().supportImageUploader()) _buildImageUploadButton( context, setting, customColors), + if (widget.leftSideToolsBuilder != null) + ...widget.leftSideToolsBuilder!(), ], ), // 聊天输入框 diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 49085a74..d384f752 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -29,6 +29,8 @@ class ChatPreview extends StatefulWidget { final MessageStateManager stateManager; final List? helpWidgets; final Widget? robotAvatar; + final Widget? Function(Message message)? avatarBuilder; + final Widget? Function(Message message)? senderNameBuilder; final bool supportBloc; final void Function(Message message)? onSpeakEvent; final void Function(Message message)? onResentEvent; @@ -41,6 +43,8 @@ class ChatPreview extends StatefulWidget { required this.controller, required this.stateManager, this.robotAvatar, + this.avatarBuilder, + this.senderNameBuilder, this.helpWidgets, this.onSpeakEvent, this.onResentEvent, @@ -217,8 +221,7 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (widget.robotAvatar != null && message.role == Role.receiver) - widget.robotAvatar!, + buildAvatar(message), ConstrainedBox( constraints: BoxConstraints( maxWidth: _chatBoxMaxWidth(context) - 80, @@ -227,6 +230,9 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (message.role == Role.receiver && + widget.senderNameBuilder != null) + widget.senderNameBuilder!(message) ?? const SizedBox(), Wrap( crossAxisAlignment: WrapCrossAlignment.end, children: [ @@ -276,6 +282,17 @@ class _ChatPreviewState extends State { ), child: Builder( builder: (context) { + if (message.statusPending() && + message.text.isEmpty) { + return const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + ), + ); + } + var text = message.text; if (!message.isReady && text != '') { text += ' ▌'; @@ -403,6 +420,21 @@ class _ChatPreviewState extends State { ); } + Widget buildAvatar(Message message) { + if (widget.avatarBuilder != null) { + final avatar = widget.avatarBuilder!(message); + if (avatar != null) { + return avatar; + } + } + + if (widget.robotAvatar != null && message.role == Role.receiver) { + return widget.robotAvatar!; + } + + return const SizedBox(); + } + /// 点击消息后控制操作弹窗菜单 void _handleMessageTapControl( BuildContext context, diff --git a/lib/page/component/multi_item_selector.dart b/lib/page/component/multi_item_selector.dart new file mode 100644 index 00000000..7205bdda --- /dev/null +++ b/lib/page/component/multi_item_selector.dart @@ -0,0 +1,129 @@ +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; + +class MultiItemSelector extends StatefulWidget { + final Widget Function(T item)? itemAvatarBuilder; + final Widget Function(T item) itemBuilder; + final List items; + final Function(List selected)? onSubmit; + final Function(List selected)? onChanged; + final List? selectedItems; + + const MultiItemSelector({ + super.key, + required this.items, + required this.itemBuilder, + this.selectedItems, + this.onSubmit, + this.onChanged, + this.itemAvatarBuilder, + }); + + @override + State> createState() => _MultiItemSelectorState(); +} + +class _MultiItemSelectorState extends State> { + var selectedItems = []; + + @override + void initState() { + selectedItems = widget.selectedItems ?? []; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + + return Column( + children: [ + if (widget.onSubmit != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + EnhancedButton( + width: 100, + height: 40, + backgroundColor: customColors.weakTextColor, + title: AppLocale.cancel.getString(context), + onPressed: () { + context.pop(); + }, + ), + EnhancedButton( + width: 100, + height: 40, + title: AppLocale.ok.getString(context), + onPressed: () { + widget.onSubmit!(selectedItems); + }, + ), + ], + ), + Expanded( + child: ListView.separated( + itemCount: widget.items.length, + itemBuilder: (context, i) { + var item = widget.items[i]; + return ListTile( + title: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 15, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.itemAvatarBuilder != null) + widget.itemAvatarBuilder!(item), + Expanded( + child: Container( + alignment: Alignment.center, + child: widget.itemBuilder(item), + )), + SizedBox( + width: 10, + child: Icon( + Icons.check, + color: selectedItems.contains(item) + ? customColors.linkColor + : Colors.transparent, + ), + ), + ], + ), + ), + onTap: () { + setState(() { + if (selectedItems.contains(item)) { + selectedItems.remove(item); + } else { + selectedItems.add(item); + } + + if (widget.onChanged != null) { + widget.onChanged!(selectedItems); + } + }); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: customColors.columnBlockDividerColor, + ); + }, + ), + ) + ], + ); + } +} diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 997a097d..fc1e03b7 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:askaide/bloc/group_chat_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; @@ -9,6 +11,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; +import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/dialog.dart'; @@ -22,7 +25,6 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; -import 'package:go_router/go_router.dart'; class GroupChatPage extends StatefulWidget { final SettingRepository setting; @@ -48,7 +50,10 @@ class _GroupChatPageState extends State { AudioPlayerController(useRemoteAPI: false); bool showAudioPlayer = false; - List selectedMemberIds = []; + List selectedMembers = []; + List messages = []; + + Timer? timer; @override void initState() { @@ -74,6 +79,7 @@ class _GroupChatPageState extends State { @override void dispose() { + timer?.cancel(); _scrollController.dispose(); _chatPreviewController.dispose(); _audioPlayerController.dispose(); @@ -99,8 +105,6 @@ class _GroupChatPageState extends State { listenWhen: (previous, current) => current is GroupChatLoaded, listener: (context, state) { if (state is GroupChatLoaded) { - // 默认选中的成员 - selectedMemberIds = state.group.members.map((e) => e.id!).toList(); // 加载聊天记录列表 context .read() @@ -108,8 +112,8 @@ class _GroupChatPageState extends State { } }, buildWhen: (previous, current) => current is GroupChatLoaded, - builder: (context, room) { - if (room is GroupChatLoaded) { + builder: (context, groupState) { + if (groupState is GroupChatLoaded) { return SafeArea( top: false, bottom: false, @@ -121,7 +125,7 @@ class _GroupChatPageState extends State { // 聊天内容窗口 Expanded( child: _buildChatPreviewArea( - room, + groupState, customColors, _chatPreviewController.selectMode, ), @@ -152,6 +156,40 @@ class _GroupChatPageState extends State { onVoiceRecordTappedEvent: () { _audioPlayerController.stop(); }, + leftSideToolsBuilder: () { + return [ + Stack( + children: [ + IconButton( + onPressed: () async { + onModelSelect( + context, groupState, customColors); + }, + icon: const Icon(Icons.group), + color: customColors.chatInputPanelText, + splashRadius: 20, + tooltip: '选择对话的模型', + ), + if (selectedMembers.isNotEmpty) + Positioned( + right: 2, + top: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 3, vertical: 3), + child: + Text('x${selectedMembers.length}', + style: TextStyle( + fontSize: 8, + color: customColors + .weakTextColor, + )), + ), + ), + ], + ) + ]; + }, ), ), ), @@ -165,6 +203,39 @@ class _GroupChatPageState extends State { ); } + void onModelSelect( + BuildContext context, + GroupChatLoaded groupState, + CustomColors customColors, + ) { + openModalBottomSheet( + context, + (context) { + return MultiItemSelector( + itemBuilder: (item) { + return Text(item.modelName); + }, + items: groupState.group.members, + onChanged: (selected) { + setState(() { + selectedMembers = selected; + }); + }, + itemAvatarBuilder: (item) { + return _buildAvatar( + avatarUrl: item.avatarUrl, + id: item.id, + size: 30, + ); + }, + selectedItems: selectedMembers, + ); + }, + heightFactor: 0.6, + title: '选择对话的模型', + ); + } + BlocConsumer _buildChatPreviewArea( GroupChatLoaded group, CustomColors customColors, @@ -174,6 +245,12 @@ class _GroupChatPageState extends State { listenWhen: (previous, current) => current is GroupChatMessagesLoaded, listener: (context, state) { if (state is GroupChatMessagesLoaded) { + if (state.error != null) { + showErrorMessageEnhanced(context, state.error); + } + + messages = state.messages; + // 聊天内容窗口滚动到底部 if (!state.hasWaitTasks && _scrollController.hasClients) { _scrollController.animateTo( @@ -194,21 +271,34 @@ class _GroupChatPageState extends State { _inputEnabled.value = true; }); } + + // 启动定时器,定时刷新聊天记录 + timer ??= Timer.periodic(const Duration(seconds: 3), (timer) { + context + .read() + .add(GroupChatUpdateMessageStatusEvent(widget.groupId)); + }); } }, buildWhen: (prv, cur) => cur is GroupChatMessagesLoaded, builder: (context, state) { if (state is GroupChatMessagesLoaded) { - final loadedMessages = state.messages - .map((e) => Message( - Role.getRoleFromText(e.role), - e.message, - type: MessageType.text, - status: e.status ?? 1, - refId: e.pid, - ts: e.createdAt, - )) - .toList(); + final loadedMessages = state.messages.map((e) { + var member = + e.memberId != null ? group.group.findMember(e.memberId!) : null; + + return Message( + id: e.id, + Role.getRoleFromText(e.role), + e.message, + type: MessageType.getTypeFromText(e.type), + status: e.status, + refId: e.pid, + ts: e.createdAt, + avatarUrl: member?.avatarUrl, + senderName: member?.modelName, + ); + }).toList(); final messages = loadedMessages.map((e) { return MessageWithState( @@ -227,7 +317,38 @@ class _GroupChatPageState extends State { scrollController: _scrollController, controller: _chatPreviewController, stateManager: widget.stateManager, - robotAvatar: selectMode ? null : _buildAvatar(group.group), + robotAvatar: selectMode + ? null + : _buildAvatar( + avatarUrl: group.group.group.avatarUrl, + id: group.group.group.id, + ), + avatarBuilder: selectMode + ? null + : (Message message) { + if (message.avatarUrl == null) { + return null; + } + + return _buildAvatar(avatarUrl: message.avatarUrl!); + }, + senderNameBuilder: (message) { + if (message.senderName == null) { + return null; + } + + return Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.senderName!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ); + }, onDeleteMessage: (id) { handleDeleteMessage(context, id); }, @@ -257,6 +378,28 @@ class _GroupChatPageState extends State { ); } + Widget buildMembersBar(List members) { + return Container( + height: 30, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: ListView( + scrollDirection: Axis.horizontal, + shrinkWrap: true, + children: [ + for (var member in members) + Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + child: _buildAvatar( + avatarUrl: member.avatarUrl, + id: member.id, + size: 20, + ), + ), + ], + ), + ); + } + /// 构建 AppBar AppBar _buildAppBar(BuildContext context, CustomColors customColors) { return _chatPreviewController.selectMode @@ -308,18 +451,17 @@ class _GroupChatPageState extends State { ); } - Widget _buildAvatar(ChatGroup room) { - if (room.group.avatarUrl != null && - room.group.avatarUrl!.startsWith('http')) { + Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { + if (avatarUrl != null && avatarUrl.startsWith('http')) { return RemoteAvatar( - avatarUrl: imageURL(room.group.avatarUrl!, qiniuImageTypeAvatar), - size: 30, + avatarUrl: imageURL(avatarUrl, qiniuImageTypeAvatar), + size: size, ); } return RandomAvatar( - id: room.group.id, - size: 30, + id: id ?? 0, + size: size, usage: Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); @@ -335,178 +477,183 @@ class _GroupChatPageState extends State { GroupChatSendEvent( widget.groupId, text, - selectedMemberIds, + selectedMembers.map((e) => e.id!).toList(), ), ); } -} -/// 处理消息删除事件 -void handleDeleteMessage(BuildContext context, int id, {int? chatHistoryId}) { - openConfirmDialog( - context, - AppLocale.confirmDelete.getString(context), - () {}, - danger: true, - ); -} + /// 处理消息删除事件 + void handleDeleteMessage(BuildContext context, int id, {int? chatHistoryId}) { + openConfirmDialog( + context, + AppLocale.confirmDelete.getString(context), + () { + context + .read() + .add(GroupChatDeleteEvent(widget.groupId, id)); + HapticFeedbackHelper.mediumImpact(); + }, + danger: true, + ); + } -/// 重置上下文 -void handleResetContext(BuildContext context) { - // openConfirmDialog( - // context, - // AppLocale.confirmStartNewChat.getString(context), - // () { - // context.read().add(ChatMessageBreakContextEvent()); - HapticFeedbackHelper.mediumImpact(); - // }, - // ); -} + /// 重置上下文 + void handleResetContext(BuildContext context) { + context.read().add(GroupChatSendSystemEvent( + widget.groupId, + MessageType.contextBreak, + message: 'context-break-message', + )); + HapticFeedbackHelper.mediumImpact(); + } -/// 清空历史消息 -void handleClearHistory(BuildContext context) { - openConfirmDialog( - context, - AppLocale.confirmClearMessages.getString(context), - () { - // context.read().add(ChatMessageClearAllEvent()); - showSuccessMessage(AppLocale.operateSuccess.getString(context)); - HapticFeedbackHelper.mediumImpact(); - }, - danger: true, - ); -} + /// 清空历史消息 + void handleClearHistory(BuildContext context) { + openConfirmDialog( + context, + AppLocale.confirmClearMessages.getString(context), + () { + context + .read() + .add(GroupChatDeleteAllEvent(widget.groupId)); + HapticFeedbackHelper.mediumImpact(); + }, + danger: true, + ); + } -/// 构建聊天内容窗口 -Widget buildSelectModeToolbars( - BuildContext context, - ChatPreviewController chatPreviewController, - CustomColors customColors, -) { - return Container( - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - topRight: Radius.circular(10), + /// 构建聊天内容窗口 + Widget buildSelectModeToolbars( + BuildContext context, + ChatPreviewController chatPreviewController, + CustomColors customColors, + ) { + return Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + ), + color: customColors.backgroundColor, ), - color: customColors.backgroundColor, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - TextButton.icon( - onPressed: () { - var messages = chatPreviewController.selectedMessages(); - if (messages.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); - return; - } - var shareText = messages.map((e) { - if (e.message.role == Role.sender) { - return e.message.text; + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton.icon( + onPressed: () { + var messages = chatPreviewController.selectedMessages(); + if (messages.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; } + var shareText = messages.map((e) { + if (e.message.role == Role.sender) { + return e.message.text; + } - return e.message.text; - }).join('\n\n'); + return e.message.text; + }).join('\n\n'); - shareTo( - context, - content: shareText, - title: AppLocale.chatHistory.getString(context), - ); - }, - icon: Icon(Icons.share, color: customColors.linkColor), - label: Text( - AppLocale.share.getString(context), - style: TextStyle(color: customColors.linkColor), - ), - ), - TextButton.icon( - onPressed: () { - chatPreviewController.selectAllMessage(); - }, - icon: Icon(Icons.select_all_outlined, color: customColors.linkColor), - label: Text( - AppLocale.selectAll.getString(context), - style: TextStyle(color: customColors.linkColor), + shareTo( + context, + content: shareText, + title: AppLocale.chatHistory.getString(context), + ); + }, + icon: Icon(Icons.share, color: customColors.linkColor), + label: Text( + AppLocale.share.getString(context), + style: TextStyle(color: customColors.linkColor), + ), ), - ), - TextButton.icon( - onPressed: () { - if (chatPreviewController.selectedMessageIds.isEmpty) { - showErrorMessageEnhanced( - context, AppLocale.noMessageSelected.getString(context)); - return; - } - - openConfirmDialog( - context, - AppLocale.confirmDelete.getString(context), - () { - final ids = chatPreviewController.selectedMessageIds.toList(); - if (ids.isNotEmpty) { - // context - // .read() - // .add(ChatMessageDeleteEvent(ids)); - - showErrorMessageEnhanced( - context, AppLocale.operateSuccess.getString(context)); - - chatPreviewController.exitSelectMode(); - } - }, - danger: true, - ); - }, - icon: Icon(Icons.delete, color: customColors.linkColor), - label: Text( - AppLocale.delete.getString(context), - style: TextStyle(color: customColors.linkColor), + TextButton.icon( + onPressed: () { + chatPreviewController.selectAllMessage(); + }, + icon: + Icon(Icons.select_all_outlined, color: customColors.linkColor), + label: Text( + AppLocale.selectAll.getString(context), + style: TextStyle(color: customColors.linkColor), + ), ), - ), - ], - ), - ); -} + TextButton.icon( + onPressed: () { + if (chatPreviewController.selectedMessageIds.isEmpty) { + showErrorMessageEnhanced( + context, AppLocale.noMessageSelected.getString(context)); + return; + } -/// 构建聊天设置下拉菜单 -Widget buildChatMoreMenu( - BuildContext context, - int chatRoomId, { - bool useLocalContext = true, - bool withSetting = true, -}) { - var customColors = Theme.of(context).extension()!; - - return EnhancedPopupMenu( - items: [ - EnhancedPopupMenuItem( - title: AppLocale.newChat.getString(context), - icon: Icons.post_add, - iconColor: Colors.blue, - onTap: (ctx) { - handleResetContext(useLocalContext ? ctx : context); - }, - ), - EnhancedPopupMenuItem( - title: AppLocale.clearChatHistory.getString(context), - icon: Icons.delete_forever, - iconColor: Colors.red, - onTap: (ctx) { - handleClearHistory(useLocalContext ? ctx : context); - }, + openConfirmDialog( + context, + AppLocale.confirmDelete.getString(context), + () { + final ids = chatPreviewController.selectedMessageIds.toList(); + if (ids.isNotEmpty) { + // context + // .read() + // .add(ChatMessageDeleteEvent(ids)); + + showErrorMessageEnhanced( + context, AppLocale.operateSuccess.getString(context)); + + chatPreviewController.exitSelectMode(); + } + }, + danger: true, + ); + }, + icon: Icon(Icons.delete, color: customColors.linkColor), + label: Text( + AppLocale.delete.getString(context), + style: TextStyle(color: customColors.linkColor), + ), + ), + ], ), - if (withSetting) + ); + } + + /// 构建聊天设置下拉菜单 + Widget buildChatMoreMenu( + BuildContext context, + int chatRoomId, { + bool useLocalContext = true, + bool withSetting = true, + }) { + var customColors = Theme.of(context).extension()!; + + return EnhancedPopupMenu( + items: [ + EnhancedPopupMenuItem( + title: AppLocale.newChat.getString(context), + icon: Icons.post_add, + iconColor: Colors.blue, + onTap: (ctx) { + handleResetContext(useLocalContext ? ctx : context); + }, + ), EnhancedPopupMenuItem( - title: AppLocale.settings.getString(context), - icon: Icons.settings, - iconColor: customColors.linkColor, - onTap: (_) { - context.push('/room/$chatRoomId/setting'); + title: AppLocale.clearChatHistory.getString(context), + icon: Icons.delete_forever, + iconColor: Colors.red, + onTap: (ctx) { + handleClearHistory(useLocalContext ? ctx : context); }, ), - ], - ); + if (withSetting) + EnhancedPopupMenuItem( + title: AppLocale.settings.getString(context), + icon: Icons.settings, + iconColor: customColors.linkColor, + onTap: (_) { + // context.push('/room/$chatRoomId/setting'); + }, + ), + ], + ); + } } diff --git a/lib/page/rooms.dart b/lib/page/rooms.dart index 28037e00..25e0e83e 100644 --- a/lib/page/rooms.dart +++ b/lib/page/rooms.dart @@ -84,9 +84,12 @@ class RoomItem extends StatelessWidget { borderRadius: BorderRadius.all( Radius.circular(customColors.borderRadius ?? 8)), onTap: () { + final redirectRoute = room.roomType == 4 + ? '/group-chat/${room.id}/chat' + : '/room/${room.id}/chat'; HapticFeedbackHelper.lightImpact(); final chatRoomBloc = context.read(); - context.push('/room/${room.id}/chat').then((value) { + context.push(redirectRoute).then((value) { chatRoomBloc.add(RoomsLoadEvent()); }); }, diff --git a/lib/page/setting_screen.dart b/lib/page/setting_screen.dart index 4cd907b2..8607a72b 100644 --- a/lib/page/setting_screen.dart +++ b/lib/page/setting_screen.dart @@ -262,7 +262,7 @@ class _SettingScreenState extends State { color: Colors.grey, ), onPressed: (context) { - context.push('/group-chat/1001/chat'); + context.push('/group-chat/1006/chat'); }, ), // SettingsTile( diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 924e787d..9ca35032 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -67,7 +67,9 @@ class APIServer { if (e.response != null) { final resp = e.response!; - if (resp.data is Map && resp.data['error'] != null) { + if (resp.data is Map && + resp.data['error'] != null && + resp.statusCode != 402) { return resp.data['error'] ?? e.toString(); } @@ -1564,7 +1566,7 @@ class APIServer { Future> chatGroupMessages( int groupId, { int page = 1, - int perPage = 100, + int? perPage, }) async { return sendGetRequest( '/v1/group-chat/$groupId/messages', @@ -1577,11 +1579,15 @@ class APIServer { return PagedData( data: res, page: resp.data['page'] ?? 1, - perPage: resp.data['per_page'] ?? 100, + perPage: resp.data['per_page'], total: resp.data['total'], lastPage: resp.data['last_page'], ); }, + queryParameters: { + 'page': page, + 'per_page': perPage, + }, ); } @@ -1596,4 +1602,50 @@ class APIServer { data: req.toJson(), ); } + + /// 群聊发送系统消息 + Future chatGroupSendSystemMessage( + int groupId, { + required String messageType, + String? message, + }) async { + return sendPostRequest( + '/v1/group-chat/$groupId/chat-system', + (resp) => GroupMessage.fromJson(resp['data']), + formData: { + 'message_type': messageType, + 'message': message, + }, + ); + } + + /// 群组聊天消息状态 + Future> chatGroupMessageStatus( + int groupId, List messageIds) async { + return sendGetRequest( + '/v1/group-chat/$groupId/chat-messages', + (resp) { + var res = []; + for (var item in resp.data['data']) { + res.add(GroupMessage.fromJson(item)); + } + + return res; + }, + queryParameters: { + "message_ids": messageIds.join(','), + }, + ); + } + + /// 清空群组聊天消息 + Future chatGroupDeleteAllMessages(int groupId) async { + return sendDeleteRequest('/v1/group-chat/$groupId/all-chat', (resp) {}); + } + + /// 删除群组聊天消息 + Future chatGroupDeleteMessage(int groupId, int messageId) async { + return sendDeleteRequest( + '/v1/group-chat/$groupId/chat/$messageId', (resp) {}); + } } diff --git a/lib/repo/model/group.dart b/lib/repo/model/group.dart index 102734a4..7be71808 100644 --- a/lib/repo/model/group.dart +++ b/lib/repo/model/group.dart @@ -8,6 +8,10 @@ class ChatGroup { final RoomInServer group; final List members; + GroupMember? findMember(int memberId) { + return members.where((member) => member.id == memberId).firstOrNull; + } + ChatGroup({ required this.group, required this.members, @@ -66,11 +70,12 @@ class GroupMessage { final int id; final String message; final String role; + final String type; final int? tokenConsumed; final int? quotaConsumed; final int? pid; final int? memberId; - final int? status; + final int status; DateTime? createdAt; DateTime? updatedAt; @@ -78,11 +83,12 @@ class GroupMessage { required this.id, required this.message, required this.role, + required this.status, + required this.type, this.tokenConsumed, this.quotaConsumed, this.pid, this.memberId, - this.status, this.createdAt, this.updatedAt, }); @@ -92,11 +98,12 @@ class GroupMessage { id: json['id'], message: json['message'] ?? '', role: json['role'] == 1 ? 'user' : 'assistant', + type: json['type'] ?? 'text', tokenConsumed: json['token_consumed'], quotaConsumed: json['quota_consumed'], pid: json['pid'], memberId: json['member_id'], - status: json['status'], + status: json['status'] ?? 0, createdAt: DateTime.tryParse(json['CreatedAt']), updatedAt: DateTime.tryParse(json['UpdatedAt']), ); @@ -107,6 +114,7 @@ class GroupMessage { 'id': id, 'message': message, 'role': role, + 'type': type, 'token_consumed': tokenConsumed, 'quota_consumed': quotaConsumed, 'pid': pid, @@ -147,26 +155,24 @@ class GroupChatSendRequestMessage { } class GroupChatSendRequest { - final List messages; + final String message; final List memberIds; GroupChatSendRequest({ - required this.messages, + required this.message, required this.memberIds, }); Map toJson() { return { - 'messages': messages.map((message) => message.toJson()).toList(), + 'message': message, 'member_ids': memberIds, }; } factory GroupChatSendRequest.fromJson(Map json) { return GroupChatSendRequest( - messages: ((json['messages'] ?? []) as List) - .map((message) => GroupChatSendRequestMessage.fromJson(message)) - .toList(), + message: json['message'], memberIds: (json['member_ids'] as List).map((e) => e as int).toList(), ); } diff --git a/lib/repo/model/message.dart b/lib/repo/model/message.dart index 68f3a4d3..7b5d175e 100644 --- a/lib/repo/model/message.dart +++ b/lib/repo/model/message.dart @@ -55,6 +55,12 @@ class Message { /// 是否当前消息已就绪,不需要持久化 bool isReady = true; + /// 消息发送者的头像,不需要持久化 + String? avatarUrl; + + /// 消息发送者的名称,不需要持久化 + String? senderName; + Message( this.role, this.text, { @@ -72,6 +78,8 @@ class Message { this.status = 1, this.quotaConsumed, this.tokenConsumed, + this.avatarUrl, + this.senderName, }); /// 获取消息附加信息 diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index 504bd193..a55a1175 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -173,6 +173,7 @@ class RoomInServer { String? initMessage; int maxContext; int? maxTokens; + int? roomType; DateTime? lastActiveTime; DateTime? createdAt; DateTime? updatedAt; @@ -183,6 +184,7 @@ class RoomInServer { required this.avatarId, required this.name, required this.maxContext, + this.roomType, this.avatarUrl, this.description, this.priority, @@ -208,6 +210,7 @@ class RoomInServer { 'vendor': vendor, 'init_message': initMessage, 'max_context': maxContext, + 'room_type': roomType, 'max_tokens': maxTokens, 'system_prompt': systemPrompt, 'last_active_time': lastActiveTime?.toIso8601String(), @@ -230,6 +233,7 @@ class RoomInServer { initMessage: json['init_message'], maxContext: json['max_context'] ?? 10, maxTokens: json['max_tokens'], + roomType: json['room_type'], lastActiveTime: json['last_active_time'] != null ? DateTime.parse(json['last_active_time']) : null, diff --git a/lib/repo/model/room.dart b/lib/repo/model/room.dart index c4f4eb31..0e74beba 100644 --- a/lib/repo/model/room.dart +++ b/lib/repo/model/room.dart @@ -38,6 +38,9 @@ class Room { /// room 类型:local or remote bool? localRoom; + /// 聊天室类型 + int? roomType; + bool get isLocalRoom => localRoom ?? false; /// 聊天室头像 标识 @@ -93,6 +96,7 @@ class Room { this.systemPrompt, this.priority = 0, this.color, + this.roomType, this.initMessage, this.localRoom, this.maxContext = 10, @@ -109,6 +113,7 @@ class Room { 'priority': priority, 'icon_data': iconData, 'color': color, + 'room_type': roomType, 'description': description, 'system_prompt': systemPrompt, 'init_message': initMessage, @@ -129,6 +134,7 @@ class Room { model = (map['model'] ?? '') as String, iconData = map['icon_data'] as String?, color = map['color'] as String?, + roomType = map['room_type'] as int?, systemPrompt = map['system_prompt'] as String?, description = map['description'] as String?, initMessage = map['init_message'] as String?, From 8cc8d606c68e38db7ada3e0af65531fdc06eca48 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Fri, 20 Oct 2023 18:02:52 +0800 Subject: [PATCH 03/28] update --- lib/bloc/room_bloc.dart | 26 ++- lib/bloc/room_event.dart | 12 + lib/bloc/room_state.dart | 7 + lib/main.dart | 19 ++ lib/page/chat_room_create.dart | 11 + lib/page/group/chat.dart | 32 +-- lib/page/group/create.dart | 402 +++++++++++++++++++++++++++++++++ lib/page/rooms.dart | 2 +- lib/repo/api_server.dart | 22 ++ lib/repo/model/misc.dart | 4 + 10 files changed, 519 insertions(+), 18 deletions(-) create mode 100644 lib/page/group/create.dart diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index 86654cae..1c876658 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -3,6 +3,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/group.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; @@ -116,7 +117,9 @@ class RoomBloc extends BlocExt { // 加载聊天室列表 on((event, emit) async { - emit(RoomsLoading()); + if (!event.forceRefresh) { + emit(RoomsLoading()); + } emit(await createRoomsLoadedState(cache: !event.forceRefresh)); }); @@ -267,6 +270,27 @@ class RoomBloc extends BlocExt { emit(RoomGalleriesLoaded(const [], error: e)); } }); + + // 创建群聊聊天室 + on((event, emit) async { + emit(RoomsLoading()); + + try { + await APIServer().createGroupRoom( + name: event.name, + avatarUrl: event.avatarUrl, + members: (event.members ?? []) + .map((e) => GroupMember(modelId: e.id, modelName: e.shortName)) + .toList(), + ); + + emit(GroupRoomCreateResultState(true)); + emit(await createRoomsLoadedState(cache: false)); + } catch (e) { + emit(GroupRoomCreateResultState(false, error: e)); + emit(RoomsLoaded(const [], error: e.toString())); + } + }); } Future createRoomsLoadedState({bool cache = true}) async { diff --git a/lib/bloc/room_event.dart b/lib/bloc/room_event.dart index 8fce8999..da81bb7a 100644 --- a/lib/bloc/room_event.dart +++ b/lib/bloc/room_event.dart @@ -29,6 +29,18 @@ class RoomCreateEvent extends RoomEvent { }); } +class GroupRoomCreateEvent extends RoomEvent { + final String name; + final String? avatarUrl; + final List? members; + + GroupRoomCreateEvent({ + required this.name, + this.avatarUrl, + this.members, + }); +} + class RoomDeleteEvent extends RoomEvent { final int roomId; diff --git a/lib/bloc/room_state.dart b/lib/bloc/room_state.dart index 96bfdf8c..010b71ce 100644 --- a/lib/bloc/room_state.dart +++ b/lib/bloc/room_state.dart @@ -44,3 +44,10 @@ class RoomGalleriesLoaded extends RoomState { RoomGalleriesLoaded(this.galleries, {this.error, this.tags = const []}); } + +class GroupRoomCreateResultState extends RoomState { + final bool success; + final Object? error; + + GroupRoomCreateResultState(this.success, {this.error}); +} diff --git a/lib/main.dart b/lib/main.dart index e33f6581..39e443de 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,6 +35,7 @@ import 'package:askaide/page/creative_island/creative_island_history_preview.dar import 'package:askaide/page/custom_home_models.dart'; import 'package:askaide/page/free_statistics.dart'; import 'package:askaide/page/group/chat.dart'; +import 'package:askaide/page/group/create.dart'; import 'package:askaide/page/lab/creative_models.dart'; import 'package:askaide/page/destroy_account.dart'; import 'package:askaide/page/diagnosis.dart'; @@ -836,6 +837,24 @@ class MyApp extends StatefulWidget { ); }, ), + GoRoute( + name: 'group-chat-create', + path: '/group-chat-create', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + BlocProvider.value(value: chatRoomBloc), + ], + child: GroupCreatePage(setting: settingRepo), + ), + ); + }, + ), ], ) ], diff --git a/lib/page/chat_room_create.dart b/lib/page/chat_room_create.dart index 07dc243d..b70c12ec 100644 --- a/lib/page/chat_room_create.dart +++ b/lib/page/chat_room_create.dart @@ -105,6 +105,17 @@ class _ChatRoomCreateScreenState extends State { backgroundColor: customColors.backgroundContainerColor, centerTitle: true, toolbarHeight: CustomSize.toolbarHeight, + actions: [ + if (Ability().supportAPIServer() && !Ability().supportLocalOpenAI()) + IconButton( + icon: const Icon(Icons.group_add), + onPressed: () { + context.push('/group-chat-create').whenComplete(() { + context.read().add(RoomsLoadEvent()); + }); + }, + ), + ], ), body: BackgroundContainer( setting: widget.setting, diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index fc1e03b7..9b92a5f0 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -170,22 +170,22 @@ class _GroupChatPageState extends State { splashRadius: 20, tooltip: '选择对话的模型', ), - if (selectedMembers.isNotEmpty) - Positioned( - right: 2, - top: 0, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 3, vertical: 3), - child: - Text('x${selectedMembers.length}', - style: TextStyle( - fontSize: 8, - color: customColors - .weakTextColor, - )), - ), + Positioned( + right: 2, + top: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 3, vertical: 3), + child: Text( + selectedMembers.isNotEmpty + ? 'x${selectedMembers.length}' + : '随机', + style: TextStyle( + fontSize: 7, + color: customColors.weakTextColor, + )), ), + ), ], ) ]; @@ -445,7 +445,7 @@ class _GroupChatPageState extends State { }, ), actions: [ - buildChatMoreMenu(context, widget.groupId), + buildChatMoreMenu(context, widget.groupId, withSetting: false), ], toolbarHeight: CustomSize.toolbarHeight, ); diff --git a/lib/page/group/create.dart b/lib/page/group/create.dart new file mode 100644 index 00000000..13556b90 --- /dev/null +++ b/lib/page/group/create.dart @@ -0,0 +1,402 @@ +import 'dart:io'; +import 'dart:math'; + +import 'package:askaide/bloc/room_bloc.dart'; +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/helper/upload.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/avatar_selector.dart'; +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/column_block.dart'; +import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/enhanced_input.dart'; +import 'package:askaide/page/component/enhanced_textfield.dart'; +import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/multi_item_selector.dart'; +import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; + +class GroupCreatePage extends StatefulWidget { + final SettingRepository setting; + + const GroupCreatePage({super.key, required this.setting}); + + @override + State createState() => _GroupCreatePageState(); +} + +class _GroupCreatePageState extends State { + final _nameController = TextEditingController(text: ''); + + String? _avatarUrl; + List avatarPresets = []; + + final randomSeed = Random().nextInt(10000); + + List models = []; + List selectedModels = []; + + Function? globalLoadingCancel; + + @override + void initState() { + super.initState(); + + // 加载预定义头像 + APIServer().avatars().then((value) { + avatarPresets = value; + }); + + // 加载模型 + APIServer().models().then((value) { + setState(() { + models = value; + }); + }); + + context.read().add(RoomGalleriesLoadEvent()); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return Scaffold( + appBar: AppBar( + title: const Text( + '创建群组', + style: TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + centerTitle: true, + elevation: 0, + toolbarHeight: CustomSize.toolbarHeight, + ), + backgroundColor: customColors.backgroundContainerColor, + body: BackgroundContainer( + setting: widget.setting, + enabled: false, + child: BlocListener( + listenWhen: (previous, current) => + current is GroupRoomCreateResultState, + listener: (context, state) { + if (state is GroupRoomCreateResultState) { + globalLoadingCancel?.call(); + if (state.success) { + showSuccessMessage(AppLocale.operateSuccess.getString(context)); + context.pop(); + } else { + showErrorMessageEnhanced(context, + state.error ?? AppLocale.operateFailed.getString(context)); + } + } + }, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + ColumnBlock( + children: [ + // 名称 + EnhancedTextField( + customColors: customColors, + controller: _nameController, + maxLength: 50, + maxLines: 1, + showCounter: false, + labelText: '群组名称', + labelPosition: LabelPosition.left, + hintText: AppLocale.required.getString(context), + ), + EnhancedInput( + padding: const EdgeInsets.only(top: 10, bottom: 5), + title: Text( + '群组头像', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, + ), + ), + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: _avatarUrl == null + ? null + : DecorationImage( + image: (_avatarUrl!.startsWith('http') + ? CachedNetworkImageProviderEnhanced( + _avatarUrl!) + : FileImage(File( + _avatarUrl!))) as ImageProvider, + fit: BoxFit.cover, + ), + ), + child: _avatarUrl == null + ? const Center( + child: Icon( + Icons.interests, + color: Colors.grey, + ), + ) + : const SizedBox(), + ), + ], + ), + onPressed: () { + openModalBottomSheet( + context, + (context) { + return AvatarSelector( + onSelected: (selected) { + setState(() { + _avatarUrl = selected.url; + }); + context.pop(); + }, + usage: AvatarUsage.room, + randomSeed: randomSeed, + defaultAvatarUrl: _avatarUrl, + externalAvatarUrls: [ + ...avatarPresets, + ], + ); + }, + heightFactor: 0.8, + ); + }, + ), + ], + ), + ColumnBlock( + children: [ + // 成员 + EnhancedInput( + padding: const EdgeInsets.only(top: 10, bottom: 5), + title: Text( + '模型成员', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, + ), + ), + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Container( + width: + resolveSelectedModelsPreviewWidth(context), + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + child: buildSelectedModelsPreview(), + ), + Positioned( + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: customColors.tagsBackground, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + selectedModels.isEmpty + ? '全部' + : 'x${selectedModels.length}', + style: TextStyle( + fontSize: 8, + color: customColors.weakTextColor, + ), + ), + ), + ) + ], + ), + ], + ), + onPressed: () { + openModalBottomSheet( + context, + (context) { + return MultiItemSelector( + itemBuilder: (item) { + return Text(item.shortName); + }, + items: models, + onChanged: (selected) { + setState(() { + selectedModels = selected; + }); + }, + itemAvatarBuilder: (item) { + return _buildAvatar( + avatarUrl: item.avatarUrl, size: 30); + }, + selectedItems: selectedModels, + ); + }, + heightFactor: 0.6, + title: '选择模型', + ); + }, + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: EnhancedButton( + title: AppLocale.ok.getString(context), + onPressed: () async { + globalLoadingCancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: + AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), + ); + + final name = _nameController.text.trim(); + if (name == '') { + globalLoadingCancel?.call(); + showErrorMessage('请输入群组名称'); + return; + } + + try { + if (_avatarUrl != null) { + if (!(_avatarUrl!.startsWith('http://') || + _avatarUrl!.startsWith('https://'))) { + // 上传文件,获取 URL + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return const LoadingIndicator( + message: "正在上传图片,请稍后...", + ); + }, + allowClick: false, + ); + + final uploadRes = + await ImageUploader(widget.setting) + .upload(_avatarUrl!, usage: 'avatar') + .whenComplete(() => cancel()); + _avatarUrl = uploadRes.url; + } + } + + if (context.mounted) { + context.read().add( + GroupRoomCreateEvent( + name: name, + avatarUrl: _avatarUrl, + members: selectedModels, + ), + ); + } + } catch (e) { + globalLoadingCancel?.call(); + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + } + }, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + } + + Widget buildSelectedModelsPreview() { + if (selectedModels.isEmpty) { + return const Center( + child: Icon( + Icons.group, + color: Colors.grey, + ), + ); + } + + return Stack( + clipBehavior: Clip.none, + children: [ + for (var i = 0; i < selectedModels.length; i++) + i == 0 + ? _buildAvatar( + avatarUrl: selectedModels.first.avatarUrl, + size: 30, + ) + : Positioned( + left: i * 15.0, + child: _buildAvatar( + avatarUrl: selectedModels[i].avatarUrl, + size: 30, + ), + ), + ], + ); + } + + Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { + if (avatarUrl != null && avatarUrl.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(avatarUrl, qiniuImageTypeAvatar), + size: size, + ); + } + + return RandomAvatar( + id: id ?? 0, + size: size, + usage: + Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + ); + } + + double resolveSelectedModelsPreviewWidth(BuildContext context) { + final maxSize = MediaQuery.of(context).size.width - 180; + final expectSize = 45.0 + selectedModels.length * 15; + + return expectSize > maxSize ? maxSize : expectSize; + } +} diff --git a/lib/page/rooms.dart b/lib/page/rooms.dart index 25e0e83e..90efab22 100644 --- a/lib/page/rooms.dart +++ b/lib/page/rooms.dart @@ -90,7 +90,7 @@ class RoomItem extends StatelessWidget { HapticFeedbackHelper.lightImpact(); final chatRoomBloc = context.read(); context.push(redirectRoute).then((value) { - chatRoomBloc.add(RoomsLoadEvent()); + chatRoomBloc.add(RoomsLoadEvent(forceRefresh: true)); }); }, child: Row( diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 9ca35032..055ae0f7 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -1041,6 +1041,28 @@ class APIServer { ); } + /// 创建群聊房间 + Future createGroupRoom({ + required String name, + String? description, + String? avatarUrl, + List? members, + }) async { + return sendPostJSONRequest( + '/v1/group-chat', + (resp) => resp.data["group_id"], + data: { + 'name': name, + 'avatar_url': avatarUrl, + 'members': members?.map((e) => e.toJson()).toList(), + }, + finallyCallback: () { + HttpClient.cacheManager + .deleteByPrimaryKey('$url/v2/rooms', requestMethod: 'GET'); + }, + ); + } + /// 创建房间 Future createRoom({ required String name, diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index a55a1175..f848ec96 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -476,6 +476,7 @@ class Model { bool isImage; bool disabled; String? tag; + String? avatarUrl; Model({ required this.id, @@ -487,6 +488,7 @@ class Model { this.description, this.disabled = false, this.tag, + this.avatarUrl, }); toJson() => { @@ -499,6 +501,7 @@ class Model { 'is_image': isImage, 'disabled': disabled, 'tag': tag, + 'avatar_url': avatarUrl, }; static Model fromJson(Map json) { @@ -512,6 +515,7 @@ class Model { isImage: json['is_image'], disabled: json['disabled'] ?? false, tag: json['tag'], + avatarUrl: json['avatar_url'], ); } } From 129d9fcaa6cad80e15d886f11501abd854599d6b Mon Sep 17 00:00:00 2001 From: mylxsw Date: Fri, 20 Oct 2023 19:51:52 +0800 Subject: [PATCH 04/28] update --- lib/bloc/group_chat_bloc.dart | 58 ++++++++++++++++++++++++++++++---- lib/bloc/group_chat_event.dart | 7 +++- lib/helper/cache.dart | 12 +++++++ lib/page/group/chat.dart | 5 ++- lib/repo/api_server.dart | 4 ++- lib/repo/model/group.dart | 4 +-- 6 files changed, 76 insertions(+), 14 deletions(-) diff --git a/lib/bloc/group_chat_bloc.dart b/lib/bloc/group_chat_bloc.dart index 35291048..3e167cfb 100644 --- a/lib/bloc/group_chat_bloc.dart +++ b/lib/bloc/group_chat_bloc.dart @@ -1,3 +1,6 @@ +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'; @@ -23,7 +26,27 @@ class GroupChatBloc extends Bloc { // 加载聊天组聊天记录 on((event, emit) async { - await refreshGroupMessages(event.groupId, page: event.page); + if (event.isInitRequest) { + try { + final cached = + await Cache().stringGet(key: 'group:speed:${event.groupId}'); + if (cached != null) { + final messages = (jsonDecode(cached) as List) + .map((e) => GroupMessage.fromJson(e)) + .toList(); + + emit(GroupChatMessagesLoaded(messages: messages)); + } + } catch (e) { + Logger.instance.e(e); + } + } + + await refreshGroupMessages( + event.groupId, + page: event.page, + forceRefresh: true, + ); emit(GroupChatMessagesLoaded(messages: messages)); }); @@ -41,10 +64,18 @@ class GroupChatBloc extends Bloc { Logger.instance.d(resp.toJson()); - await refreshGroupMessages(event.groupId, page: 1); + await refreshGroupMessages( + event.groupId, + page: 1, + forceRefresh: true, + ); emit(GroupChatMessagesLoaded(messages: messages)); } catch (e) { - await refreshGroupMessages(event.groupId, page: 1); + await refreshGroupMessages( + event.groupId, + page: 1, + forceRefresh: true, + ); emit(GroupChatMessagesLoaded(messages: messages, error: e)); } }); @@ -60,7 +91,11 @@ class GroupChatBloc extends Bloc { Logger.instance.d(resp.toJson()); } finally { - await refreshGroupMessages(event.groupId, page: 1); + await refreshGroupMessages( + event.groupId, + page: 1, + forceRefresh: true, + ); emit(GroupChatMessagesLoaded(messages: messages)); } }); @@ -108,9 +143,18 @@ class GroupChatBloc extends Bloc { }); } - refreshGroupMessages(int groupId, {int page = 1}) async { - final data = await APIServer().chatGroupMessages(groupId, page: page); - + refreshGroupMessages( + int groupId, { + int page = 1, + bool forceRefresh = false, + }) async { + final data = await APIServer() + .chatGroupMessages(groupId, page: page, cache: !forceRefresh); messages = data.data.reversed.toList(); + + if (page == 1) { + Cache() + .setString(key: 'group:speed:$groupId', value: jsonEncode(messages)); + } } } diff --git a/lib/bloc/group_chat_event.dart b/lib/bloc/group_chat_event.dart index 5387e174..63e3914c 100644 --- a/lib/bloc/group_chat_event.dart +++ b/lib/bloc/group_chat_event.dart @@ -12,8 +12,13 @@ class GroupChatLoadEvent extends GroupChatEvent { class GroupChatMessagesLoadEvent extends GroupChatEvent { final int groupId; final int page; + final bool isInitRequest; - GroupChatMessagesLoadEvent(this.groupId, {this.page = 1}); + GroupChatMessagesLoadEvent( + this.groupId, { + this.page = 1, + this.isInitRequest = false, + }); } class GroupChatSendEvent extends GroupChatEvent { diff --git a/lib/helper/cache.dart b/lib/helper/cache.dart index 56a42beb..de773e3d 100644 --- a/lib/helper/cache.dart +++ b/lib/helper/cache.dart @@ -42,4 +42,16 @@ class Cache { }) async { await cacheRepo.set(key, value.toString(), duration); } + + Future setString({ + required String key, + required String value, + Duration duration = const Duration(days: 1), + }) async { + await cacheRepo.set(key, value, duration); + } + + Future stringGet({required String key}) async { + return await cacheRepo.get(key); + } } diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 9b92a5f0..59323c19 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -106,9 +106,8 @@ class _GroupChatPageState extends State { listener: (context, state) { if (state is GroupChatLoaded) { // 加载聊天记录列表 - context - .read() - .add(GroupChatMessagesLoadEvent(widget.groupId)); + context.read().add( + GroupChatMessagesLoadEvent(widget.groupId, isInitRequest: true)); } }, buildWhen: (previous, current) => current is GroupChatLoaded, diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 055ae0f7..e68663b7 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -1589,8 +1589,9 @@ class APIServer { int groupId, { int page = 1, int? perPage, + bool cache = true, }) async { - return sendGetRequest( + return sendCachedGetRequest( '/v1/group-chat/$groupId/messages', (resp) { var res = []; @@ -1610,6 +1611,7 @@ class APIServer { 'page': page, 'per_page': perPage, }, + forceRefresh: !cache, ); } diff --git a/lib/repo/model/group.dart b/lib/repo/model/group.dart index 7be71808..41f151ca 100644 --- a/lib/repo/model/group.dart +++ b/lib/repo/model/group.dart @@ -120,8 +120,8 @@ class GroupMessage { 'pid': pid, 'member_id': memberId, 'status': status, - 'created_at': createdAt?.toIso8601String(), - 'updated_at': updatedAt?.toIso8601String(), + 'CreatedAt': createdAt?.toIso8601String(), + 'UpdatedAt': updatedAt?.toIso8601String(), }; } } From 2f83cb4f56f3e4bca1ad5b3a530d6e3ce1f3862c Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sun, 22 Oct 2023 14:35:08 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E7=BE=A4=E7=BB=84=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/group_chat_bloc.dart | 3 +- lib/bloc/group_chat_event.dart | 3 +- lib/bloc/room_bloc.dart | 26 +- lib/bloc/room_event.dart | 16 +- lib/bloc/room_state.dart | 4 +- lib/helper/http.dart | 14 + lib/main.dart | 22 ++ lib/page/group/chat.dart | 11 +- lib/page/group/create.dart | 15 +- lib/page/group/edit.dart | 471 +++++++++++++++++++++++++++++++++ lib/page/rooms.dart | 158 ++++++----- lib/repo/api_server.dart | 49 ++++ lib/repo/model/group.dart | 4 + lib/repo/model/misc.dart | 4 + 14 files changed, 719 insertions(+), 81 deletions(-) create mode 100644 lib/page/group/edit.dart diff --git a/lib/bloc/group_chat_bloc.dart b/lib/bloc/group_chat_bloc.dart index 3e167cfb..fe8c51f1 100644 --- a/lib/bloc/group_chat_bloc.dart +++ b/lib/bloc/group_chat_bloc.dart @@ -19,7 +19,8 @@ class GroupChatBloc extends Bloc { GroupChatBloc({required this.stateManager}) : super(GroupChatInitial()) { // 加载聊天组 on((event, emit) async { - final group = await APIServer().chatGroup(event.groupId); + final group = + await APIServer().chatGroup(event.groupId, cache: !event.forceUpdate); final states = await stateManager.loadRoomStates(event.groupId); emit(GroupChatLoaded(group: group, states: states)); }); diff --git a/lib/bloc/group_chat_event.dart b/lib/bloc/group_chat_event.dart index 63e3914c..2aa7ed68 100644 --- a/lib/bloc/group_chat_event.dart +++ b/lib/bloc/group_chat_event.dart @@ -5,8 +5,9 @@ sealed class GroupChatEvent {} class GroupChatLoadEvent extends GroupChatEvent { final int groupId; + final bool forceUpdate; - GroupChatLoadEvent(this.groupId); + GroupChatLoadEvent(this.groupId, {this.forceUpdate = false}); } class GroupChatMessagesLoadEvent extends GroupChatEvent { diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index 1c876658..d572a040 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -279,18 +279,34 @@ class RoomBloc extends BlocExt { await APIServer().createGroupRoom( name: event.name, avatarUrl: event.avatarUrl, - members: (event.members ?? []) - .map((e) => GroupMember(modelId: e.id, modelName: e.shortName)) - .toList(), + members: event.members, ); - emit(GroupRoomCreateResultState(true)); + emit(GroupRoomUpdateResultState(true)); emit(await createRoomsLoadedState(cache: false)); } catch (e) { - emit(GroupRoomCreateResultState(false, error: e)); + emit(GroupRoomUpdateResultState(false, error: e)); emit(RoomsLoaded(const [], error: e.toString())); } }); + + // 群聊聊天室更新 + on((event, emit) async { + emit(RoomsLoading()); + + try { + await APIServer().updateGroupRoom( + groupId: event.groupId, + name: event.name, + avatarUrl: event.avatarUrl, + members: event.members, + ); + + emit(GroupRoomUpdateResultState(true)); + } catch (e) { + emit(GroupRoomUpdateResultState(false, error: e)); + } + }); } Future createRoomsLoadedState({bool cache = true}) async { diff --git a/lib/bloc/room_event.dart b/lib/bloc/room_event.dart index da81bb7a..44500cc6 100644 --- a/lib/bloc/room_event.dart +++ b/lib/bloc/room_event.dart @@ -32,7 +32,7 @@ class RoomCreateEvent extends RoomEvent { class GroupRoomCreateEvent extends RoomEvent { final String name; final String? avatarUrl; - final List? members; + final List? members; GroupRoomCreateEvent({ required this.name, @@ -41,6 +41,20 @@ class GroupRoomCreateEvent extends RoomEvent { }); } +class GroupRoomUpdateEvent extends RoomEvent { + final int groupId; + final String name; + final String? avatarUrl; + final List? members; + + GroupRoomUpdateEvent({ + required this.groupId, + required this.name, + this.avatarUrl, + this.members, + }); +} + class RoomDeleteEvent extends RoomEvent { final int roomId; diff --git a/lib/bloc/room_state.dart b/lib/bloc/room_state.dart index 010b71ce..b822eb75 100644 --- a/lib/bloc/room_state.dart +++ b/lib/bloc/room_state.dart @@ -45,9 +45,9 @@ class RoomGalleriesLoaded extends RoomState { RoomGalleriesLoaded(this.galleries, {this.error, this.tags = const []}); } -class GroupRoomCreateResultState extends RoomState { +class GroupRoomUpdateResultState extends RoomState { final bool success; final Object? error; - GroupRoomCreateResultState(this.success, {this.error}); + GroupRoomUpdateResultState(this.success, {this.error}); } diff --git a/lib/helper/http.dart b/lib/helper/http.dart index bf300015..75babc3e 100644 --- a/lib/helper/http.dart +++ b/lib/helper/http.dart @@ -121,6 +121,20 @@ class HttpClient { ); } + static Future putJSON( + String url, { + Map? queryParameters, + Map? data, + Options? options, + }) async { + return await dio.put( + url, + queryParameters: queryParameters, + data: data, + options: options, + ); + } + static Future delete( String url, { Map? queryParameters, diff --git a/lib/main.dart b/lib/main.dart index 39e443de..16a7e23c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,6 +36,7 @@ import 'package:askaide/page/custom_home_models.dart'; import 'package:askaide/page/free_statistics.dart'; import 'package:askaide/page/group/chat.dart'; import 'package:askaide/page/group/create.dart'; +import 'package:askaide/page/group/edit.dart'; import 'package:askaide/page/lab/creative_models.dart'; import 'package:askaide/page/destroy_account.dart'; import 'package:askaide/page/diagnosis.dart'; @@ -855,6 +856,27 @@ class MyApp extends StatefulWidget { ); }, ), + GoRoute( + name: 'group-chat-edit', + path: '/group-chat/:group_id/edit', + pageBuilder: (context, state) { + return transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: ((context) => + GroupChatBloc(stateManager: messageStateManager)), + ), + BlocProvider.value(value: chatRoomBloc), + ], + child: GroupEditPage( + setting: settingRepo, + groupId: int.tryParse(state.pathParameters['group_id']!)!, + ), + ), + ); + }, + ), ], ) ], diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 59323c19..438869bf 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -25,6 +25,7 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; class GroupChatPage extends StatefulWidget { final SettingRepository setting; @@ -214,7 +215,7 @@ class _GroupChatPageState extends State { itemBuilder: (item) { return Text(item.modelName); }, - items: groupState.group.members, + items: groupState.group.members.where((e) => e.status != 2).toList(), onChanged: (selected) { setState(() { selectedMembers = selected; @@ -444,7 +445,7 @@ class _GroupChatPageState extends State { }, ), actions: [ - buildChatMoreMenu(context, widget.groupId, withSetting: false), + buildChatMoreMenu(context, widget.groupId), ], toolbarHeight: CustomSize.toolbarHeight, ); @@ -649,7 +650,11 @@ class _GroupChatPageState extends State { icon: Icons.settings, iconColor: customColors.linkColor, onTap: (_) { - // context.push('/room/$chatRoomId/setting'); + context.push('/group-chat/$chatRoomId/edit').whenComplete(() { + context + .read() + .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + }); }, ), ], diff --git a/lib/page/group/create.dart b/lib/page/group/create.dart index 13556b90..6940dba9 100644 --- a/lib/page/group/create.dart +++ b/lib/page/group/create.dart @@ -21,6 +21,7 @@ import 'package:askaide/page/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/group.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; @@ -66,8 +67,6 @@ class _GroupCreatePageState extends State { models = value; }); }); - - context.read().add(RoomGalleriesLoadEvent()); } @override @@ -95,9 +94,9 @@ class _GroupCreatePageState extends State { enabled: false, child: BlocListener( listenWhen: (previous, current) => - current is GroupRoomCreateResultState, + current is GroupRoomUpdateResultState, listener: (context, state) { - if (state is GroupRoomCreateResultState) { + if (state is GroupRoomUpdateResultState) { globalLoadingCancel?.call(); if (state.success) { showSuccessMessage(AppLocale.operateSuccess.getString(context)); @@ -278,7 +277,7 @@ class _GroupCreatePageState extends State { children: [ Expanded( child: EnhancedButton( - title: AppLocale.ok.getString(context), + title: AppLocale.save.getString(context), onPressed: () async { globalLoadingCancel = BotToast.showCustomLoading( toastBuilder: (cancel) { @@ -325,7 +324,11 @@ class _GroupCreatePageState extends State { GroupRoomCreateEvent( name: name, avatarUrl: _avatarUrl, - members: selectedModels, + members: selectedModels + .map((e) => GroupMember( + modelId: e.realModelId, + modelName: e.shortName)) + .toList(), ), ); } diff --git a/lib/page/group/edit.dart b/lib/page/group/edit.dart new file mode 100644 index 00000000..505cf20f --- /dev/null +++ b/lib/page/group/edit.dart @@ -0,0 +1,471 @@ +import 'package:askaide/bloc/group_chat_bloc.dart'; +import 'package:askaide/repo/model/group.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:flutter/material.dart'; +import 'dart:io'; +import 'dart:math'; + +import 'package:askaide/bloc/room_bloc.dart'; +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/helper/upload.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/avatar_selector.dart'; +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/column_block.dart'; +import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/enhanced_input.dart'; +import 'package:askaide/page/component/enhanced_textfield.dart'; +import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/multi_item_selector.dart'; +import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; + +class ModelWithMemberId { + final Model model; + final int? memberId; + + ModelWithMemberId(this.model, this.memberId); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ModelWithMemberId && + runtimeType == other.runtimeType && + model == other.model && + memberId == other.memberId; +} + +class GroupEditPage extends StatefulWidget { + final SettingRepository setting; + final int groupId; + + const GroupEditPage({ + super.key, + required this.groupId, + required this.setting, + }); + + @override + State createState() => _GroupEditPageState(); +} + +class _GroupEditPageState extends State { + final _nameController = TextEditingController(text: ''); + + String? _avatarUrl; + List avatarPresets = []; + + final randomSeed = Random().nextInt(10000); + + List models = []; + List selectedModels = []; + + Function? globalLoadingCancel; + + @override + void initState() { + super.initState(); + + // 加载预定义头像 + APIServer().avatars().then((value) { + avatarPresets = value; + }); + + // 加载模型 + APIServer().models().then((value) { + setState(() { + models = value; + }); + context + .read() + .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + }); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return Scaffold( + appBar: AppBar( + title: const Text( + '群组设置', + style: TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + centerTitle: true, + elevation: 0, + toolbarHeight: CustomSize.toolbarHeight, + ), + backgroundColor: customColors.backgroundContainerColor, + body: BackgroundContainer( + setting: widget.setting, + enabled: false, + child: BlocListener( + listenWhen: (previous, current) => + current is GroupRoomUpdateResultState, + listener: (context, state) { + if (state is GroupRoomUpdateResultState) { + globalLoadingCancel?.call(); + if (state.success) { + showSuccessMessage(AppLocale.operateSuccess.getString(context)); + } else { + showErrorMessageEnhanced(context, + state.error ?? AppLocale.operateFailed.getString(context)); + } + + context + .read() + .add(GroupChatLoadEvent(widget.groupId, forceUpdate: true)); + } + }, + child: BlocConsumer( + listenWhen: (previous, current) => current is GroupChatLoaded, + listener: (context, state) { + if (state is GroupChatLoaded) { + _nameController.text = state.group.group.name; + _avatarUrl = state.group.group.avatarUrl; + selectedModels = state.group.members + .where((e) => e.status != 2) + .map((e) { + final mod = models + .where((em) => e.modelId == em.realModelId) + .firstOrNull; + if (mod == null) { + return null; + } + + return ModelWithMemberId(mod, e.id); + }) + .where((e) => e != null) + .map((e) => e!) + .toList(); + } + }, + builder: (context, state) { + return SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + ColumnBlock( + children: [ + // 名称 + EnhancedTextField( + customColors: customColors, + controller: _nameController, + maxLength: 50, + maxLines: 1, + showCounter: false, + labelText: '群组名称', + labelPosition: LabelPosition.left, + hintText: AppLocale.required.getString(context), + ), + EnhancedInput( + padding: const EdgeInsets.only(top: 10, bottom: 5), + title: Text( + '群组头像', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, + ), + ), + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 45, + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + image: _avatarUrl == null + ? null + : DecorationImage( + image: (_avatarUrl!.startsWith('http') + ? CachedNetworkImageProviderEnhanced( + _avatarUrl!) + : FileImage(File( + _avatarUrl!))) as ImageProvider, + fit: BoxFit.cover, + ), + ), + child: _avatarUrl == null + ? const Center( + child: Icon( + Icons.interests, + color: Colors.grey, + ), + ) + : const SizedBox(), + ), + ], + ), + onPressed: () { + openModalBottomSheet( + context, + (context) { + return AvatarSelector( + onSelected: (selected) { + setState(() { + _avatarUrl = selected.url; + }); + context.pop(); + }, + usage: AvatarUsage.room, + randomSeed: randomSeed, + defaultAvatarUrl: _avatarUrl, + externalAvatarUrls: [ + ...avatarPresets, + ], + ); + }, + heightFactor: 0.8, + ); + }, + ), + ], + ), + ColumnBlock( + children: [ + // 成员 + EnhancedInput( + padding: const EdgeInsets.only(top: 10, bottom: 5), + title: Text( + '模型成员', + style: TextStyle( + color: customColors.textfieldLabelColor, + fontSize: 16, + ), + ), + value: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Stack( + children: [ + Container( + width: resolveSelectedModelsPreviewWidth( + context), + height: 45, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + ), + alignment: Alignment.center, + clipBehavior: Clip.hardEdge, + child: buildSelectedModelsPreview(), + ), + Positioned( + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: customColors.tagsBackground, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + selectedModels.isEmpty + ? '全部' + : 'x${selectedModels.length}', + style: TextStyle( + fontSize: 8, + color: customColors.weakTextColor, + ), + ), + ), + ) + ], + ), + ], + ), + onPressed: () { + openModalBottomSheet( + context, + (context) { + return MultiItemSelector( + itemBuilder: (item) { + return Text(item.model.shortName); + }, + items: models + .map((e) => ModelWithMemberId( + e, + selectedModels + .where( + (se) => se.model.id == e.id) + .firstOrNull + ?.memberId)) + .toList(), + onChanged: (selected) { + setState(() { + selectedModels = selected; + }); + }, + itemAvatarBuilder: (item) { + return _buildAvatar( + avatarUrl: item.model.avatarUrl, + size: 30, + ); + }, + selectedItems: selectedModels, + ); + }, + heightFactor: 0.6, + title: '选择模型', + ); + }, + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: EnhancedButton( + title: AppLocale.save.getString(context), + onPressed: () async { + globalLoadingCancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait + .getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), + ); + + final name = _nameController.text.trim(); + if (name == '') { + globalLoadingCancel?.call(); + showErrorMessage('请输入群组名称'); + return; + } + + try { + if (_avatarUrl != null) { + if (!(_avatarUrl!.startsWith('http://') || + _avatarUrl!.startsWith('https://'))) { + // 上传文件,获取 URL + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return const LoadingIndicator( + message: "正在上传图片,请稍后...", + ); + }, + allowClick: false, + ); + + final uploadRes = + await ImageUploader(widget.setting) + .upload(_avatarUrl!, + usage: 'avatar') + .whenComplete(() => cancel()); + _avatarUrl = uploadRes.url; + } + } + + if (context.mounted) { + context.read().add( + GroupRoomUpdateEvent( + groupId: widget.groupId, + name: name, + avatarUrl: _avatarUrl, + members: selectedModels + .map((e) => GroupMember( + modelId: e.model.realModelId, + modelName: e.model.shortName, + id: e.memberId)) + .toList(), + ), + ); + } + } catch (e) { + globalLoadingCancel?.call(); + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + } + }, + ), + ), + ], + ), + ], + ), + ); + }, + ), + ), + ), + ); + } + + Widget buildSelectedModelsPreview() { + if (selectedModels.isEmpty) { + return const Center( + child: Icon( + Icons.group, + color: Colors.grey, + ), + ); + } + + return Stack( + clipBehavior: Clip.none, + children: [ + for (var i = 0; i < selectedModels.length; i++) + i == 0 + ? _buildAvatar( + avatarUrl: selectedModels.first.model.avatarUrl, + size: 30, + ) + : Positioned( + left: i * 15.0, + child: _buildAvatar( + avatarUrl: selectedModels[i].model.avatarUrl, + size: 30, + ), + ), + ], + ); + } + + Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { + if (avatarUrl != null && avatarUrl.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(avatarUrl, qiniuImageTypeAvatar), + size: size, + ); + } + + return RandomAvatar( + id: id ?? 0, + size: size, + usage: + Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + ); + } + + double resolveSelectedModelsPreviewWidth(BuildContext context) { + final maxSize = MediaQuery.of(context).size.width - 180; + final expectSize = 45.0 + selectedModels.length * 15; + + return expectSize > maxSize ? maxSize : expectSize; + } +} diff --git a/lib/page/rooms.dart b/lib/page/rooms.dart index 90efab22..a8a2ae27 100644 --- a/lib/page/rooms.dart +++ b/lib/page/rooms.dart @@ -50,7 +50,11 @@ class RoomItem extends StatelessWidget { icon: Icons.edit, onPressed: (_) { final chatRoomBloc = context.read(); - context.push('/room/${room.id}/setting').then((value) { + final redirectUrl = room.roomType == 4 + ? '/group-chat/${room.id}/edit' + : '/room/${room.id}/setting'; + + context.push(redirectUrl).then((value) { chatRoomBloc.add(RoomsLoadEvent()); }); }, @@ -93,76 +97,68 @@ class RoomItem extends StatelessWidget { chatRoomBloc.add(RoomsLoadEvent(forceRefresh: true)); }); }, - child: Row( - mainAxisSize: MainAxisSize.min, + child: Stack( children: [ - _buildAvatar(room), - Expanded( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - room.name, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - humanTime(room.lastActiveTime), - style: TextStyle( - color: - customColors.weakLinkColor?.withAlpha(65), - fontSize: 12, - ), - ), - ], - ), - const SizedBox(height: 5), - Column( + Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildAvatar(room), + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (room.systemPrompt != null && - room.systemPrompt != '') - Text( - room.systemPrompt!, - style: TextStyle( - color: customColors.weakLinkColor - ?.withAlpha(150), - fontSize: 13, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + room.name, + overflow: TextOverflow.ellipsis, + ), ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - // if (room.description != null) - // Text( - // room.description!, - // style: - // Theme.of(context).textTheme.bodySmall, - // ), - if (room.systemPrompt == null || - room.systemPrompt == '') - Text( - room - .modelName() - .toUpperCase() - .replaceAll('-TURBO', ''), - style: TextStyle( - color: customColors.weakLinkColor - ?.withAlpha(150), - fontSize: 13, + Text( + humanTime(room.lastActiveTime), + style: TextStyle( + color: customColors.weakLinkColor + ?.withAlpha(65), + fontSize: 10, + ), ), - ), + ], + ), + const SizedBox(height: 5), + buildRoomDesc(customColors), ], ), - ], + ), ), - ), + ], ), + if (room.roomType == 4) + Positioned( + right: 0, + top: 0, + child: Container( + decoration: BoxDecoration( + color: customColors.backgroundContainerColor, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(8), + bottomLeft: Radius.circular(8), + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + child: Text( + '群组', + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 8, + ), + ), + ), + ) ], ), ), @@ -171,6 +167,44 @@ class RoomItem extends StatelessWidget { ); } + Widget buildRoomDesc(CustomColors customColors) { + if (room.description != null && room.description != '') { + return Text( + room.description!, + style: TextStyle( + color: customColors.weakLinkColor?.withAlpha(150), + fontSize: 13, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + if (room.systemPrompt != null && room.systemPrompt != '') { + return Text( + room.systemPrompt!, + style: TextStyle( + color: customColors.weakLinkColor?.withAlpha(150), + fontSize: 13, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + } + + if (room.systemPrompt == null || room.systemPrompt == '') { + Text( + room.modelName().toUpperCase().replaceAll('-TURBO', ''), + style: TextStyle( + color: customColors.weakLinkColor?.withAlpha(150), + fontSize: 13, + ), + ); + } + + return const SizedBox(); + } + Widget _buildAvatar(Room room) { if (room.avatarUrl != null && room.avatarUrl!.startsWith('http')) { return SizedBox( diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index e68663b7..55ac5b4c 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -233,6 +233,28 @@ class APIServer { ); } + Future sendPutJSONRequest( + String endpoint, + T Function(dynamic) parser, { + String? subKey, + Duration duration = const Duration(days: 1), + Map? queryParameters, + Map? data, + bool forceRefresh = false, + VoidCallback? finallyCallback, + }) async { + return request( + HttpClient.putJSON( + '$url$endpoint', + queryParameters: queryParameters, + data: data, + options: _buildRequestOptions(), + ), + parser, + finallyCallback: finallyCallback, + ); + } + Future sendDeleteRequest( String endpoint, T Function(dynamic) parser, { @@ -1063,6 +1085,33 @@ class APIServer { ); } + /// 更新群聊房间 + Future updateGroupRoom({ + required int groupId, + required String name, + String? description, + String? avatarUrl, + List? members, + }) async { + return sendPutJSONRequest( + '/v1/group-chat/$groupId', + (resp) {}, + data: { + 'name': name, + 'avatar_url': avatarUrl, + 'members': members?.map((e) => e.toJson()).toList(), + }, + finallyCallback: () { + HttpClient.cacheManager + .deleteByPrimaryKey('$url/v2/rooms', requestMethod: 'GET'); + + HttpClient.cacheManager.deleteByPrimaryKey( + '$url/v1/group-chat/$groupId', + requestMethod: 'GET'); + }, + ); + } + /// 创建房间 Future createRoom({ required String name, diff --git a/lib/repo/model/group.dart b/lib/repo/model/group.dart index 41f151ca..7410a13a 100644 --- a/lib/repo/model/group.dart +++ b/lib/repo/model/group.dart @@ -39,12 +39,14 @@ class GroupMember { final String modelId; final String modelName; final String? avatarUrl; + final int? status; GroupMember({ this.id, required this.modelId, required this.modelName, this.avatarUrl, + this.status, }); factory GroupMember.fromJson(Map json) { @@ -53,6 +55,7 @@ class GroupMember { modelId: json['model_id'], modelName: json['model_name'], avatarUrl: json['avatar_url'], + status: json['status'], ); } @@ -62,6 +65,7 @@ class GroupMember { 'model_id': modelId, 'model_name': modelName, 'avatar_url': avatarUrl, + 'status': status, }; } } diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index f848ec96..5882d0d4 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -478,6 +478,10 @@ class Model { String? tag; String? avatarUrl; + String get realModelId { + return id.split(':').last; + } + Model({ required this.id, required this.name, From e7be16cd6ed5ed36f117684ac94cb235cbb353a7 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Mon, 23 Oct 2023 16:13:01 +0800 Subject: [PATCH 06/28] update --- lib/bloc/chat_chat_bloc.dart | 2 +- lib/page/chat_chat.dart | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index 9f279ff1..17d2a019 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -16,7 +16,7 @@ class ChatChatBloc extends Bloc { on((event, emit) async { final histories = await _chatMessageRepository.recentChatHistories( chatAnywhereRoomId, - 30, + 10, userId: APIServer().localUserID(), ); diff --git a/lib/page/chat_chat.dart b/lib/page/chat_chat.dart index 03564121..1599622d 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat_chat.dart @@ -241,6 +241,15 @@ class _ChatChatScreenState extends State { color: customColors.backgroundInvertedColor, ), ), + actions: [ + // IconButton( + // icon: Icon( + // Icons.history, + // color: customColors.backgroundInvertedColor, + // ), + // onPressed: () {}, + // ), + ], backgroundImage: Image.asset( customColors.appBarBackgroundImage!, fit: BoxFit.cover, @@ -262,9 +271,9 @@ class _ChatChatScreenState extends State { bottom: false, child: Container( margin: - const EdgeInsets.only(top: 20, left: 15), + const EdgeInsets.only(top: 10, left: 15), child: Text( - '历史记录', + '最近历史记录', style: TextStyle( color: customColors.weakTextColor ?.withAlpha(100), From e683a75efd0d53962ecbe6033386e9bdc140882f Mon Sep 17 00:00:00 2001 From: mylxsw Date: Mon, 23 Oct 2023 17:04:33 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E7=A7=BB=E5=8A=A8=E5=88=B0?= =?UTF-8?q?=E5=8D=95=E7=8B=AC=E7=9A=84=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 17 +++ lib/page/chat_chat.dart | 20 +-- lib/page/chat_history.dart | 134 +++++++++++++++++++++ lib/page/chat_room_create.dart | 2 + lib/page/component/sliver_component.dart | 8 +- lib/page/data/chat_history_datasource.dart | 60 +++++++++ lib/repo/chat_message_repo.dart | 2 + lib/repo/data/chat_history.dart | 3 +- 8 files changed, 236 insertions(+), 10 deletions(-) create mode 100644 lib/page/chat_history.dart create mode 100644 lib/page/data/chat_history_datasource.dart diff --git a/lib/main.dart b/lib/main.dart index 16a7e23c..4ac9da47 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -23,6 +23,7 @@ import 'package:askaide/page/bind_phone_page.dart'; import 'package:askaide/page/change_password.dart'; import 'package:askaide/page/chat_anywhere.dart'; import 'package:askaide/page/chat_chat.dart'; +import 'package:askaide/page/chat_history.dart'; import 'package:askaide/page/chat_room_create.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/transition_resolver.dart'; @@ -388,6 +389,22 @@ class MyApp extends StatefulWidget { ), ), ), + GoRoute( + name: 'chat_chat_history', + path: '/chat-chat/history', + pageBuilder: (context, state) => transitionResolver( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => ChatChatBloc(chatMsgRepo)), + ], + child: ChatHistoryPage( + setting: settingRepo, + chatMessageRepo: chatMsgRepo, + ), + ), + ), + ), GoRoute( path: '/lab/avatar-selector', pageBuilder: (context, state) => transitionResolver( diff --git a/lib/page/chat_chat.dart b/lib/page/chat_chat.dart index 1599622d..16fc2303 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat_chat.dart @@ -242,13 +242,19 @@ class _ChatChatScreenState extends State { ), ), actions: [ - // IconButton( - // icon: Icon( - // Icons.history, - // color: customColors.backgroundInvertedColor, - // ), - // onPressed: () {}, - // ), + IconButton( + icon: Icon( + Icons.history, + color: customColors.backgroundInvertedColor, + ), + onPressed: () { + context.push('/chat-chat/history').whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); + }); + }, + ), ], backgroundImage: Image.asset( customColors.appBarBackgroundImage!, diff --git a/lib/page/chat_history.dart b/lib/page/chat_history.dart new file mode 100644 index 00000000..69b03978 --- /dev/null +++ b/lib/page/chat_history.dart @@ -0,0 +1,134 @@ +import 'package:askaide/bloc/chat_chat_bloc.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/chat_chat.dart'; +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/data/chat_history_datasource.dart'; +import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/repo/chat_message_repo.dart'; +import 'package:askaide/repo/model/chat_history.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; +import 'package:loading_more_list/loading_more_list.dart'; +import 'package:provider/provider.dart'; + +class ChatHistoryPage extends StatefulWidget { + final SettingRepository setting; + final ChatMessageRepository chatMessageRepo; + + const ChatHistoryPage( + {super.key, required this.setting, required this.chatMessageRepo}); + + @override + State createState() => _ChatHistoryPageState(); +} + +class _ChatHistoryPageState extends State { + late final ChatHistoryDatasource datasource; + + @override + void initState() { + datasource = ChatHistoryDatasource(widget.chatMessageRepo); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + + return Scaffold( + appBar: AppBar( + title: Text( + AppLocale.histories.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + toolbarHeight: CustomSize.toolbarHeight, + centerTitle: true, + ), + backgroundColor: customColors.backgroundContainerColor, + body: BackgroundContainer( + setting: widget.setting, + enabled: false, + child: RefreshIndicator( + color: customColors.linkColor, + onRefresh: () async { + await datasource.refresh(); + }, + child: BlocListener( + listenWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, + listener: (context, state) { + if (state is ChatChatRecentHistoriesLoaded) { + datasource.refresh(); + } + }, + child: RefreshIndicator( + color: customColors.linkColor, + displacement: 20, + onRefresh: () { + return datasource.refresh(); + }, + child: LoadingMoreList( + ListConfig( + itemBuilder: (context, item, index) { + return ChatHistoryItem( + history: item, + customColors: customColors, + onTap: () { + context + .push( + '/chat-anywhere?chat_id=${item.id}&model=${item.model}') + .whenComplete(() { + FocusScope.of(context).requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); + }); + }, + ); + }, + sourceList: datasource, + indicatorBuilder: (context, status) { + String msg = ''; + switch (status) { + case IndicatorStatus.noMoreLoad: + msg = '~ 没有更多了 ~'; + break; + case IndicatorStatus.loadingMoreBusying: + msg = '加载中...'; + break; + case IndicatorStatus.error: + msg = '加载失败,请稍后再试'; + break; + case IndicatorStatus.empty: + msg = '暂无数据'; + break; + default: + return const Center(child: LoadingIndicator()); + } + return Container( + padding: const EdgeInsets.all(15), + alignment: Alignment.center, + child: Text( + msg, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 14, + ), + ), + ); + }, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/page/chat_room_create.dart b/lib/page/chat_room_create.dart index b70c12ec..a979a9b5 100644 --- a/lib/page/chat_room_create.dart +++ b/lib/page/chat_room_create.dart @@ -115,6 +115,7 @@ class _ChatRoomCreateScreenState extends State { }); }, ), + const SizedBox(width: 8), ], ), body: BackgroundContainer( @@ -145,6 +146,7 @@ class _ChatRoomCreateScreenState extends State { isScrollable: true, labelColor: customColors.linkColor, indicator: const BoxDecoration(), + labelPadding: EdgeInsets.only(right: 15), overlayColor: MaterialStateProperty.all(Colors.transparent), ), diff --git a/lib/page/component/sliver_component.dart b/lib/page/component/sliver_component.dart index 0ee61855..8d8d8a51 100644 --- a/lib/page/component/sliver_component.dart +++ b/lib/page/component/sliver_component.dart @@ -34,7 +34,9 @@ class SliverSingleComponent extends StatelessWidget { pinned: true, snap: false, primary: true, - actions: actions, + actions: (actions ?? []).isEmpty + ? null + : [...actions!, const SizedBox(width: 8)], backgroundColor: customColors.backgroundContainerColor, flexibleSpace: FlexibleSpaceBar( title: title, @@ -94,7 +96,9 @@ class SliverComponent extends StatelessWidget { pinned: true, snap: false, primary: true, - actions: actions, + actions: (actions ?? []).isEmpty + ? null + : [...actions!, const SizedBox(width: 8)], backgroundColor: customColors.backgroundContainerColor, flexibleSpace: FlexibleSpaceBar( title: title, diff --git a/lib/page/data/chat_history_datasource.dart b/lib/page/data/chat_history_datasource.dart new file mode 100644 index 00000000..40a16414 --- /dev/null +++ b/lib/page/data/chat_history_datasource.dart @@ -0,0 +1,60 @@ +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/logger.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:loading_more_list/loading_more_list.dart'; + +class ChatHistoryDatasource extends LoadingMoreBase { + int pageindex = 1; + bool _hasMore = true; + bool forceRefresh = false; + + final ChatMessageRepository repo; + ChatHistoryDatasource(this.repo); + + @override + bool get hasMore => (_hasMore && length < 300) || forceRefresh; + + @override + Future loadData([bool isloadMoreAction = false]) async { + try { + final histories = await repo.recentChatHistories( + chatAnywhereRoomId, + 30, + offset: 30 * (pageindex - 1), + userId: APIServer().localUserID(), + ); + + if (pageindex == 1) { + clear(); + } + + for (var element in histories) { + add(element); + } + + if (histories.isEmpty) { + _hasMore = false; + } + + pageindex = pageindex + 1; + return true; + } catch (e) { + Logger.instance.e(e); + return false; + } + } + + @override + Future refresh([bool notifyStateChanged = false]) async { + _hasMore = true; + pageindex = 1; + //force to refresh list when you don't want clear list before request + //for the case, if your list already has 20 items. + forceRefresh = !notifyStateChanged; + var result = await super.refresh(notifyStateChanged); + forceRefresh = false; + return result; + } +} diff --git a/lib/repo/chat_message_repo.dart b/lib/repo/chat_message_repo.dart index b66d09e2..8adbd9a6 100644 --- a/lib/repo/chat_message_repo.dart +++ b/lib/repo/chat_message_repo.dart @@ -149,11 +149,13 @@ class ChatMessageRepository { int roomId, int count, { int? userId, + int? offset, }) async { return await _chatHistoryProvider.getChatHistories( roomId, count, userId: userId, + offset: offset, ); } diff --git a/lib/repo/data/chat_history.dart b/lib/repo/data/chat_history.dart index adb48ab3..053fd2fa 100644 --- a/lib/repo/data/chat_history.dart +++ b/lib/repo/data/chat_history.dart @@ -6,7 +6,7 @@ class ChatHistoryProvider { ChatHistoryProvider(this.conn); Future> getChatHistories(int roomId, int count, - {int? userId}) async { + {int? userId, int? offset}) async { final userConditon = userId == null ? ' AND user_id IS NULL' : ' AND user_id = $userId'; @@ -16,6 +16,7 @@ class ChatHistoryProvider { whereArgs: [roomId], orderBy: 'updated_at DESC', limit: count, + offset: offset, ); return histories.map((e) => ChatHistory.fromMap(e)).toList(); From 9ae9973cdf9c66b99056b13900e0f1b0bad72a60 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Mon, 23 Oct 2023 17:59:13 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 3 + lib/page/chat_anywhere.dart | 82 +++++------- lib/page/chat_chat.dart | 2 +- lib/page/chat_history.dart | 136 ++++++++++---------- lib/page/chat_room_create.dart | 3 +- lib/page/component/multi_item_selector.dart | 6 +- lib/page/group/create.dart | 2 +- lib/page/setting_screen.dart | 33 ++--- lib/repo/openai_repo.dart | 4 + 9 files changed, 129 insertions(+), 142 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 4ac9da47..086fa89a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -365,6 +365,9 @@ class MyApp extends StatefulWidget { model: state.queryParameters['model'] == '' ? null : state.queryParameters['model'], + title: state.queryParameters['title'] == '' + ? null + : state.queryParameters['title'], ), ), ), diff --git a/lib/page/chat_anywhere.dart b/lib/page/chat_anywhere.dart index 1955d6fc..76c3f751 100644 --- a/lib/page/chat_anywhere.dart +++ b/lib/page/chat_anywhere.dart @@ -3,6 +3,7 @@ import 'package:askaide/bloc/free_count_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/model.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/chat_screen.dart'; import 'package:askaide/page/component/audio_player.dart'; @@ -24,6 +25,7 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; +import 'package:askaide/repo/model/model.dart' as mm; class ChatAnywhereScreen extends StatefulWidget { final MessageStateManager stateManager; @@ -31,6 +33,7 @@ class ChatAnywhereScreen extends StatefulWidget { final int? chatId; final String? initialMessage; final String? model; + final String? title; const ChatAnywhereScreen({ super.key, @@ -39,6 +42,7 @@ class ChatAnywhereScreen extends StatefulWidget { this.chatId, this.initialMessage, this.model, + this.title, }); @override @@ -56,6 +60,8 @@ class _ChatAnywhereScreenState extends State { bool showAudioPlayer = false; + List supportModels = []; + @override void initState() { chatId = widget.chatId; @@ -91,6 +97,13 @@ class _ChatAnywhereScreenState extends State { }); }; + // 加载模型列表,用于查询模型名称 + ModelAggregate.models().then((value) { + setState(() { + supportModels = value; + }); + }); + super.initState(); } @@ -173,45 +186,29 @@ class _ChatAnywhereScreenState extends State { if (state is ChatMessagesLoaded) { return Column( children: [ - Text( - AppLocale.chatAnywhere.getString(context), - style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + Container( + width: MediaQuery.of(context).size.width / 2, + alignment: Alignment.center, + child: Text( + widget.title ?? AppLocale.chatAnywhere.getString(context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: + const TextStyle(fontSize: CustomSize.appBarTitleSize), + ), ), - // BlocBuilder( - // buildWhen: (previous, current) => current is RoomLoaded, - // builder: (context, state) { - // if (state is RoomLoaded) { - // return BlocBuilder( - // buildWhen: (previous, current) => - // current is FreeCountLoadedState, - // builder: (context, freeState) { - // if (freeState is FreeCountLoadedState) { - // final matched = freeState.model(state.room.model); - // if (matched != null && - // matched.leftCount > 0 && - // matched.maxCount > 0) { - // return Text( - // '今日剩余免费 ${matched.leftCount} 次', - // style: TextStyle( - // color: customColors.weakTextColor, - // fontSize: 12, - // ), - // ); - // } - // } - // return const SizedBox(); - // }, - // ); - // } - // return const SizedBox(); - // }, - // ), - // if (state.chatHistory != null && - // state.chatHistory!.model != null) - // Text( - // state.chatHistory!.model ?? '', - // style: const TextStyle(fontSize: 12), - // ), + if (state.chatHistory?.model != null) + Text( + supportModels + .where((e) => e.id == state.chatHistory!.model!) + .firstOrNull + ?.shortName ?? + '', + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 10, + ), + ) ], ); } @@ -219,15 +216,6 @@ class _ChatAnywhereScreenState extends State { return const SizedBox(); }, ), - - // actions: [ - // buildChatMoreMenu( - // context, - // chatAnywhereRoomId, - // useLocalContext: false, - // withSetting: false, - // ), - // ], flexibleSpace: SizedBox( width: double.infinity, child: ShaderMask( diff --git a/lib/page/chat_chat.dart b/lib/page/chat_chat.dart index 16fc2303..83dfec88 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat_chat.dart @@ -298,7 +298,7 @@ class _ChatChatScreenState extends State { onTap: () { context .push( - '/chat-anywhere?chat_id=${state.histories[index - 1].id}&model=${state.histories[index - 1].model}') + '/chat-anywhere?chat_id=${state.histories[index - 1].id}&model=${state.histories[index - 1].model}&title=${state.histories[index - 1].title}') .whenComplete(() { FocusScope.of(context) .requestFocus(FocusNode()); diff --git a/lib/page/chat_history.dart b/lib/page/chat_history.dart index 69b03978..6dfa504f 100644 --- a/lib/page/chat_history.dart +++ b/lib/page/chat_history.dart @@ -14,7 +14,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; import 'package:loading_more_list/loading_more_list.dart'; -import 'package:provider/provider.dart'; class ChatHistoryPage extends StatefulWidget { final SettingRepository setting; @@ -54,75 +53,80 @@ class _ChatHistoryPageState extends State { body: BackgroundContainer( setting: widget.setting, enabled: false, - child: RefreshIndicator( - color: customColors.linkColor, - onRefresh: () async { - await datasource.refresh(); - }, - child: BlocListener( - listenWhen: (previous, current) => - current is ChatChatRecentHistoriesLoaded, - listener: (context, state) { - if (state is ChatChatRecentHistoriesLoaded) { - datasource.refresh(); - } + child: SafeArea( + top: false, + left: false, + right: false, + child: RefreshIndicator( + color: customColors.linkColor, + onRefresh: () async { + await datasource.refresh(); }, - child: RefreshIndicator( - color: customColors.linkColor, - displacement: 20, - onRefresh: () { - return datasource.refresh(); + child: BlocListener( + listenWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, + listener: (context, state) { + if (state is ChatChatRecentHistoriesLoaded) { + datasource.refresh(); + } }, - child: LoadingMoreList( - ListConfig( - itemBuilder: (context, item, index) { - return ChatHistoryItem( - history: item, - customColors: customColors, - onTap: () { - context - .push( - '/chat-anywhere?chat_id=${item.id}&model=${item.model}') - .whenComplete(() { - FocusScope.of(context).requestFocus(FocusNode()); + child: RefreshIndicator( + color: customColors.linkColor, + displacement: 20, + onRefresh: () { + return datasource.refresh(); + }, + child: LoadingMoreList( + ListConfig( + itemBuilder: (context, item, index) { + return ChatHistoryItem( + history: item, + customColors: customColors, + onTap: () { context - .read() - .add(ChatChatLoadRecentHistories()); - }); - }, - ); - }, - sourceList: datasource, - indicatorBuilder: (context, status) { - String msg = ''; - switch (status) { - case IndicatorStatus.noMoreLoad: - msg = '~ 没有更多了 ~'; - break; - case IndicatorStatus.loadingMoreBusying: - msg = '加载中...'; - break; - case IndicatorStatus.error: - msg = '加载失败,请稍后再试'; - break; - case IndicatorStatus.empty: - msg = '暂无数据'; - break; - default: - return const Center(child: LoadingIndicator()); - } - return Container( - padding: const EdgeInsets.all(15), - alignment: Alignment.center, - child: Text( - msg, - style: TextStyle( - color: customColors.weakTextColor, - fontSize: 14, + .push( + '/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') + .whenComplete(() { + FocusScope.of(context).requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); + }); + }, + ); + }, + sourceList: datasource, + indicatorBuilder: (context, status) { + String msg = ''; + switch (status) { + case IndicatorStatus.noMoreLoad: + msg = '~ 没有更多了 ~'; + break; + case IndicatorStatus.loadingMoreBusying: + msg = '加载中...'; + break; + case IndicatorStatus.error: + msg = '加载失败,请稍后再试'; + break; + case IndicatorStatus.empty: + msg = '暂无数据'; + break; + default: + return const Center(child: LoadingIndicator()); + } + return Container( + padding: const EdgeInsets.all(15), + alignment: Alignment.center, + child: Text( + msg, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 14, + ), ), - ), - ); - }, + ); + }, + ), ), ), ), diff --git a/lib/page/chat_room_create.dart b/lib/page/chat_room_create.dart index a979a9b5..dbeaeb7d 100644 --- a/lib/page/chat_room_create.dart +++ b/lib/page/chat_room_create.dart @@ -146,7 +146,8 @@ class _ChatRoomCreateScreenState extends State { isScrollable: true, labelColor: customColors.linkColor, indicator: const BoxDecoration(), - labelPadding: EdgeInsets.only(right: 15), + labelPadding: + const EdgeInsets.only(right: 5, left: 10), overlayColor: MaterialStateProperty.all(Colors.transparent), ), diff --git a/lib/page/component/multi_item_selector.dart b/lib/page/component/multi_item_selector.dart index 7205bdda..3eaec44b 100644 --- a/lib/page/component/multi_item_selector.dart +++ b/lib/page/component/multi_item_selector.dart @@ -73,10 +73,8 @@ class _MultiItemSelectorState extends State> { return ListTile( title: Container( alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 15, - ), + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, diff --git a/lib/page/group/create.dart b/lib/page/group/create.dart index 6940dba9..60ecd89d 100644 --- a/lib/page/group/create.dart +++ b/lib/page/group/create.dart @@ -260,7 +260,7 @@ class _GroupCreatePageState extends State { }, itemAvatarBuilder: (item) { return _buildAvatar( - avatarUrl: item.avatarUrl, size: 30); + avatarUrl: item.avatarUrl, size: 40); }, selectedItems: selectedModels, ); diff --git a/lib/page/setting_screen.dart b/lib/page/setting_screen.dart index 8607a72b..1710e45d 100644 --- a/lib/page/setting_screen.dart +++ b/lib/page/setting_screen.dart @@ -129,17 +129,17 @@ class _SettingScreenState extends State { }, ), // 诊断 - SettingsTile( - title: Text(AppLocale.diagnostic.getString(context)), - trailing: Icon( - CupertinoIcons.chevron_forward, - size: MediaQuery.of(context).textScaleFactor * 18, - color: Colors.grey, - ), - onPressed: (context) { - context.push('/diagnosis'); - }, - ), + // SettingsTile( + // title: Text(AppLocale.diagnostic.getString(context)), + // trailing: Icon( + // CupertinoIcons.chevron_forward, + // size: MediaQuery.of(context).textScaleFactor * 18, + // color: Colors.grey, + // ), + // onPressed: (context) { + // context.push('/diagnosis'); + // }, + // ), // 检查更新 if (!PlatformTool.isIOS()) SettingsTile( @@ -254,17 +254,6 @@ class _SettingScreenState extends State { }, ), - SettingsTile( - title: const Text('群组'), - trailing: Icon( - CupertinoIcons.chevron_forward, - size: MediaQuery.of(context).textScaleFactor * 18, - color: Colors.grey, - ), - onPressed: (context) { - context.push('/group-chat/1006/chat'); - }, - ), // SettingsTile( // title: const Text('用户中心'), // trailing: Icon( diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index ea908f1b..11d84018 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -86,6 +86,7 @@ class OpenAIRepository { category: modelTypeOpenAI, isChatModel: true, description: '能力最强的 GPT-3.5 模型,成本低', + shortName: 'GPT-3.5 Turbo', ), 'gpt-3.5-turbo-16k': mm.Model( 'gpt-3.5-turbo-16k', @@ -94,6 +95,7 @@ class OpenAIRepository { category: modelTypeOpenAI, isChatModel: true, description: '能力最强的 GPT-3.5 模型,成本为 gpt-3.5-turbo 的两倍,但是支持 4K 上下文', + shortName: 'GPT-3.5 Turbo 16K', ), 'gpt-4': mm.Model( 'gpt-4', @@ -102,6 +104,7 @@ class OpenAIRepository { category: modelTypeOpenAI, isChatModel: true, description: '比GPT-3.5模型更强,能够执行复杂任务,并优化用于聊天', + shortName: 'GPT-4', ), 'gpt-4-32k': mm.Model( @@ -111,6 +114,7 @@ class OpenAIRepository { category: modelTypeOpenAI, isChatModel: true, description: '基于 GPT-4,但是支持4倍的内容长度', + shortName: 'GPT-4 32K', ), // 'gpt-4-0314': Model( From 85369c34963d84c0c248278bff452533eadb7b1d Mon Sep 17 00:00:00 2001 From: mylxsw Date: Tue, 24 Oct 2023 18:06:32 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E8=81=8A=E5=A4=A9=E5=88=86=E4=BA=AB?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=9B=BE=E7=89=87=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/group_chat_bloc.dart | 52 ++- lib/bloc/group_chat_event.dart | 4 +- lib/bloc/group_chat_state.dart | 13 +- lib/main.dart | 1 + lib/page/chat_screen.dart | 4 +- lib/page/component/chat/chat_preview.dart | 151 ++++--- lib/page/component/chat/chat_share.dart | 432 ++++++++++++++++++++ lib/page/component/share.dart | 20 +- lib/page/data/group_message_datasource.dart | 55 +++ lib/page/group/chat.dart | 138 +++++-- lib/repo/api/page.dart | 14 + lib/repo/api_server.dart | 13 +- lib/repo/model/group.dart | 4 + 13 files changed, 778 insertions(+), 123 deletions(-) create mode 100644 lib/page/component/chat/chat_share.dart create mode 100644 lib/page/data/group_message_datasource.dart diff --git a/lib/bloc/group_chat_bloc.dart b/lib/bloc/group_chat_bloc.dart index fe8c51f1..1e6a3b39 100644 --- a/lib/bloc/group_chat_bloc.dart +++ b/lib/bloc/group_chat_bloc.dart @@ -22,7 +22,12 @@ class GroupChatBloc extends Bloc { final group = await APIServer().chatGroup(event.groupId, cache: !event.forceUpdate); final states = await stateManager.loadRoomStates(event.groupId); - emit(GroupChatLoaded(group: group, states: states)); + + emit(GroupChatLoaded( + group: group, + states: states, + defaultChatMembers: await loadDefaultChatMembers(event.groupId), + )); }); // 加载聊天组聊天记录 @@ -45,7 +50,7 @@ class GroupChatBloc extends Bloc { await refreshGroupMessages( event.groupId, - page: event.page, + startId: event.startId, forceRefresh: true, ); @@ -65,16 +70,24 @@ class GroupChatBloc extends Bloc { Logger.instance.d(resp.toJson()); + // 记录默认聊天成员 + updateDefaultChatMembers( + event.groupId, + resp.tasks.map((e) => e.memberId).toList(), + ).then((members) { + emit(GroupDefaultMemberSelected(members)); + }); + await refreshGroupMessages( event.groupId, - page: 1, + startId: 0, forceRefresh: true, ); emit(GroupChatMessagesLoaded(messages: messages)); } catch (e) { await refreshGroupMessages( event.groupId, - page: 1, + startId: 0, forceRefresh: true, ); emit(GroupChatMessagesLoaded(messages: messages, error: e)); @@ -94,7 +107,7 @@ class GroupChatBloc extends Bloc { } finally { await refreshGroupMessages( event.groupId, - page: 1, + startId: 0, forceRefresh: true, ); emit(GroupChatMessagesLoaded(messages: messages)); @@ -146,16 +159,39 @@ class GroupChatBloc extends Bloc { refreshGroupMessages( int groupId, { - int page = 1, + int startId = 0, bool forceRefresh = false, }) async { final data = await APIServer() - .chatGroupMessages(groupId, page: page, cache: !forceRefresh); + .chatGroupMessages(groupId, startId: startId, cache: !forceRefresh); messages = data.data.reversed.toList(); - if (page == 1) { + if (startId == 0) { Cache() .setString(key: 'group:speed:$groupId', value: jsonEncode(messages)); } } + + Future> 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> updateDefaultChatMembers( + int groupId, List members) async { + // 记录默认聊天成员 + await Cache().setString( + key: 'group:$groupId:default-members', + value: members.join(','), + duration: const Duration(days: 365), + ); + + return members; + } } diff --git a/lib/bloc/group_chat_event.dart b/lib/bloc/group_chat_event.dart index 2aa7ed68..00cb4f8f 100644 --- a/lib/bloc/group_chat_event.dart +++ b/lib/bloc/group_chat_event.dart @@ -12,12 +12,12 @@ class GroupChatLoadEvent extends GroupChatEvent { class GroupChatMessagesLoadEvent extends GroupChatEvent { final int groupId; - final int page; + final int startId; final bool isInitRequest; GroupChatMessagesLoadEvent( this.groupId, { - this.page = 1, + this.startId = 0, this.isInitRequest = false, }); } diff --git a/lib/bloc/group_chat_state.dart b/lib/bloc/group_chat_state.dart index f24969c2..d4055242 100644 --- a/lib/bloc/group_chat_state.dart +++ b/lib/bloc/group_chat_state.dart @@ -8,8 +8,19 @@ final class GroupChatInitial extends GroupChatState {} class GroupChatLoaded extends GroupChatState { final ChatGroup group; final Map states; + final List? defaultChatMembers; - GroupChatLoaded({required this.group, required this.states}); + GroupChatLoaded({ + required this.group, + required this.states, + this.defaultChatMembers, + }); +} + +class GroupDefaultMemberSelected extends GroupChatState { + final List members; + + GroupDefaultMemberSelected(this.members); } class GroupChatMessagesLoaded extends GroupChatState { diff --git a/lib/main.dart b/lib/main.dart index 086fa89a..f2b082b8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -848,6 +848,7 @@ class MyApp extends StatefulWidget { create: ((context) => GroupChatBloc(stateManager: messageStateManager)), ), + BlocProvider.value(value: chatRoomBloc), ], child: GroupChatPage( setting: settingRepo, diff --git a/lib/page/chat_screen.dart b/lib/page/chat_screen.dart index 7a1981c6..b349b6cb 100644 --- a/lib/page/chat_screen.dart +++ b/lib/page/chat_screen.dart @@ -585,10 +585,10 @@ Widget buildSelectModeToolbars( } var shareText = messages.map((e) { if (e.message.role == Role.sender) { - return e.message.text; + return '我:\n${e.message.text}'; } - return e.message.text; + return '助理:\n${e.message.text}'; }).join('\n\n'); shareTo( diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index d384f752..d662f840 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -7,6 +7,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/attached_button_panel.dart'; +import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/dialog.dart'; @@ -584,49 +585,72 @@ class _ChatPreviewState extends State { ) ], )), - if (message.role == Role.sender && widget.onResentEvent != null) - TextButton.icon( - onPressed: () { - widget.onResentEvent!(message); + TextButton.icon( + onPressed: () async { cancel(); + var messages = []; + + if (message.role == Role.receiver) { + final questions = widget.messages + .where((e) => e.message.id == message.refId) + .toList(); + if (questions.isNotEmpty) { + var q = questions.first; + messages.add(ChatShareMessage( + content: q.message.text, + leftSide: false, + )); + } + } + + messages.add(ChatShareMessage( + content: message.text, + leftSide: message.role == Role.receiver, + avatarURL: message.avatarUrl, + username: message.senderName, + )); + + if (message.role == Role.sender) { + final answers = widget.messages + .where((e) => e.message.refId == message.id) + .toList(); + if (answers.isNotEmpty) { + for (var a in answers) { + messages.add(ChatShareMessage( + content: a.message.text, + leftSide: true, + avatarURL: a.message.avatarUrl, + username: a.message.senderName, + )); + } + } + } + + Navigator.push( + context, + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => ChatShareScreen(messages: messages), + ), + ); + + // await shareTo(context, content: message.text, title: '聊天记录'); }, label: const Text(''), - icon: const Column( + icon: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.restore, + const Icon( + Icons.share, color: Color.fromARGB(255, 255, 255, 255), size: 14, ), Text( - '重发', - style: TextStyle(fontSize: 12, color: Colors.white), - ), + AppLocale.share.getString(context), + style: const TextStyle(fontSize: 12, color: Colors.white), + ) ], - ), - ) - else - TextButton.icon( - onPressed: () async { - cancel(); - await shareTo(context, content: message.text, title: '聊天记录'); - }, - label: const Text(''), - icon: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.share, - color: Color.fromARGB(255, 255, 255, 255), - size: 14, - ), - Text( - AppLocale.share.getString(context), - style: const TextStyle(fontSize: 12, color: Colors.white), - ) - ], - )), + )), TextButton.icon( onPressed: () { widget.controller.enterSelectMode(); @@ -671,25 +695,48 @@ class _ChatPreviewState extends State { ), if (Ability().supportSpeak() && widget.onSpeakEvent != null) TextButton.icon( - onPressed: () { - cancel(); - widget.onSpeakEvent!(message); - }, - label: const Text(''), - icon: const Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.record_voice_over, - color: Color.fromARGB(255, 255, 255, 255), - size: 14, - ), - Text( - '朗读', - style: TextStyle(fontSize: 12, color: Colors.white), - ) - ], - )), + onPressed: () { + cancel(); + widget.onSpeakEvent!(message); + }, + label: const Text(''), + icon: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.record_voice_over, + color: Color.fromARGB(255, 255, 255, 255), + size: 14, + ), + Text( + '朗读', + style: TextStyle(fontSize: 12, color: Colors.white), + ) + ], + ), + ), + if (message.role == Role.sender && widget.onResentEvent != null) + TextButton.icon( + onPressed: () { + widget.onResentEvent!(message); + cancel(); + }, + label: const Text(''), + icon: const Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.restore, + color: Color.fromARGB(255, 255, 255, 255), + size: 14, + ), + Text( + '重发', + style: TextStyle(fontSize: 12, color: Colors.white), + ), + ], + ), + ), ], ), ); diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart new file mode 100644 index 00000000..af7caa08 --- /dev/null +++ b/lib/page/component/chat/chat_share.dart @@ -0,0 +1,432 @@ +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/helper.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/helper/platform.dart'; +import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/component/chat/markdown.dart'; +import 'package:askaide/page/component/enhanced_popup_menu.dart'; +import 'package:askaide/page/component/image.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/random_avatar.dart'; +import 'package:askaide/page/component/share.dart'; +import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; +import 'package:bot_toast/bot_toast.dart'; +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localization/flutter_localization.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:widgets_to_image/widgets_to_image.dart'; + +class ChatShareMessage { + final String? username; + final String content; + final String? avatarURL; + final bool leftSide; + + const ChatShareMessage({ + this.username, + required this.content, + this.avatarURL, + this.leftSide = true, + }); +} + +class ChatShareScreen extends StatefulWidget { + final List messages; + const ChatShareScreen({ + super.key, + required this.messages, + }); + + @override + State createState() => _ChatShareScreenState(); +} + +class _ChatShareScreenState extends State { + final WidgetsToImageController controller = WidgetsToImageController(); + + bool showQRCode = true; + bool usingChatStyle = true; + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return Scaffold( + appBar: AppBar( + toolbarHeight: CustomSize.toolbarHeight, + actions: [ + TextButton( + onPressed: () async { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 15), + ); + + try { + final data = await controller.capture(); + if (data != null) { + final file = await writeTempFile('share-image.png', data); + cancel(); + // ignore: use_build_context_synchronously + await shareTo( + context, + content: 'images', + images: [ + file.path, + ], + ); + } + } finally { + cancel(); + } + }, + child: Row( + children: [ + Icon(Icons.share, size: 14, color: customColors.weakLinkColor), + const SizedBox(width: 5), + Text( + '分享', + style: TextStyle( + color: customColors.weakLinkColor, fontSize: 14), + ), + ], + ), + ), + EnhancedPopupMenu( + items: [ + EnhancedPopupMenuItem( + title: '保存到本地', + icon: Icons.save, + onTap: (ctx) async { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 15), + ); + + try { + final data = await controller.capture(); + if (data != null) { + cancel(); + // ignore: use_build_context_synchronously + + if (PlatformTool.isIOS() || PlatformTool.isAndroid()) { + await ImageGallerySaver.saveImage(data, quality: 100); + + showSuccessMessage('图片保存成功'); + } else { + FileSaver.instance + .saveFile( + name: randomId(), + bytes: data, + ext: 'png', + mimeType: MimeType.png, + ) + .then((value) { + showSuccessMessage('文件保存成功'); + }); + } + } + } finally { + cancel(); + } + }, + ), + EnhancedPopupMenuItem( + title: showQRCode ? '不显示邀请信息' : '显示邀请信息', + icon: showQRCode ? Icons.visibility_off : Icons.visibility, + onTap: (ctx) { + setState(() { + showQRCode = !showQRCode; + }); + }, + ), + EnhancedPopupMenuItem( + title: usingChatStyle ? '使用列表风格' : '使用聊天风格', + icon: usingChatStyle ? Icons.list : Icons.chat, + onTap: (ctx) { + setState(() { + usingChatStyle = !usingChatStyle; + }); + }, + ), + ], + ), + ], + ), + backgroundColor: customColors.backgroundContainerColor, + body: Align( + alignment: Alignment.topCenter, + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: CustomSize.smallWindowSize, + ), + child: SafeArea( + child: SingleChildScrollView( + child: FutureBuilder( + future: APIServer().shareInfo(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(resolveError(context, snapshot.error!)), + ); + } + + if (snapshot.hasData) { + return buildShareWindow(customColors, context, snapshot); + } + + return const Center( + child: Text('Loading ...'), + ); + }), + ), + ), + ), + ), + ); + } + + Widget buildShareWindow(CustomColors customColors, BuildContext context, + AsyncSnapshot snapshot) { + return Column( + children: [ + WidgetsToImage( + controller: controller, + child: Container( + color: customColors.backgroundContainerColor, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + usingChatStyle + ? buildChatPreview(context, customColors) + : buildListPreview(context, customColors), + if (showQRCode) buildQRCodePanel(customColors, snapshot), + ], + ), + ), + ), + ], + ); + } + + Widget buildQRCodePanel( + CustomColors customColors, AsyncSnapshot snapshot) { + return Container( + color: customColors.backgroundColor, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 20, + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CachedNetworkImageEnhanced( + imageUrl: snapshot.data!.qrCode, + width: 100, + height: 100, + ), + ), + const SizedBox(width: 10), + Expanded( + child: Text( + snapshot.data!.message, + ), + ), + ], + ), + ), + ); + } + + Widget buildListPreview(BuildContext context, CustomColors customColors) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: Column( + children: widget.messages.map((message) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: Align( + alignment: Alignment.topLeft, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (message.avatarURL != null) + _buildAvatar(avatarUrl: message.avatarURL), + if (message.username != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.username!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ), + ], + ), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: _chatBoxMaxWidth(context), + ), + child: Container( + margin: const EdgeInsets.fromLTRB(0, 10, 10, 7), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: message.leftSide + ? customColors.chatRoomReplyBackground + : customColors.chatRoomSenderBackground, + ), + padding: const EdgeInsets.symmetric( + horizontal: 13, + vertical: 13, + ), + child: Builder( + builder: (context) { + return Markdown(data: message.content); + }, + ), + ), + ), + ], + ), + ), + ); + }).toList(), + ), + ); + } + + Widget buildChatPreview(BuildContext context, CustomColors customColors) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: Column( + children: widget.messages.map((message) { + return Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + child: Align( + alignment: + message.leftSide ? Alignment.topLeft : Alignment.topRight, + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: _chatBoxMaxWidth(context)), + child: Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (message.avatarURL != null) + _buildAvatar(avatarUrl: message.avatarURL), + ConstrainedBox( + constraints: BoxConstraints( + maxWidth: _chatBoxMaxWidth(context) - 80, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (message.username != null) + Container( + margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), + padding: + const EdgeInsets.symmetric(horizontal: 13), + child: Text( + message.username!, + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12, + ), + ), + ), + Container( + margin: message.leftSide + ? const EdgeInsets.fromLTRB(10, 0, 0, 7) + : const EdgeInsets.fromLTRB(0, 0, 10, 7), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: message.leftSide + ? customColors.chatRoomReplyBackground + : customColors.chatRoomSenderBackground, + ), + padding: const EdgeInsets.symmetric( + horizontal: 13, + vertical: 13, + ), + child: Builder( + builder: (context) { + return Markdown(data: message.content); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }).toList(), + ), + ); + } + + /// 获取聊天框的最大宽度 + double _chatBoxMaxWidth(BuildContext context) { + var screenWidth = MediaQuery.of(context).size.width; + if (screenWidth >= CustomSize.maxWindowSize) { + return CustomSize.maxWindowSize; + } + + return screenWidth; + } + + Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { + if (avatarUrl != null && avatarUrl.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(avatarUrl, qiniuImageTypeAvatar), + size: size, + ); + } + + return RandomAvatar( + id: id ?? 0, + size: size, + usage: + Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + ); + } +} diff --git a/lib/page/component/share.dart b/lib/page/component/share.dart index aab9ad42..cfcb8c2d 100644 --- a/lib/page/component/share.dart +++ b/lib/page/component/share.dart @@ -13,7 +13,15 @@ Future shareTo( String? title, List? images, }) async { - final box = context.findRenderObject() as RenderBox?; + Rect? sharePositionOrigin; + + try { + final box = context.findRenderObject() as RenderBox?; + Rect? pos = box!.localToGlobal(Offset.zero) & box.size; + sharePositionOrigin = pos; + // ignore: empty_catches + } catch (ignored) {} + if ((PlatformTool.isIOS() || PlatformTool.isAndroid()) && await isWeChatInstalled) { // ignore: use_build_context_synchronously @@ -93,15 +101,13 @@ Future shareTo( Share.shareXFiles( [XFile(images.first)], subject: title, - sharePositionOrigin: - box!.localToGlobal(Offset.zero) & box.size, + sharePositionOrigin: sharePositionOrigin, ).whenComplete(() => context.pop()); } else { Share.share( content, subject: title, - sharePositionOrigin: - box!.localToGlobal(Offset.zero) & box.size, + sharePositionOrigin: sharePositionOrigin, ).whenComplete(() => context.pop()); } }, @@ -128,13 +134,13 @@ Future shareTo( Share.shareXFiles( [XFile(images.first)], subject: title, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + sharePositionOrigin: sharePositionOrigin, ); } else { Share.share( content, subject: title, - sharePositionOrigin: box!.localToGlobal(Offset.zero) & box.size, + sharePositionOrigin: sharePositionOrigin, ); } } diff --git a/lib/page/data/group_message_datasource.dart b/lib/page/data/group_message_datasource.dart new file mode 100644 index 00000000..8a92e7ed --- /dev/null +++ b/lib/page/data/group_message_datasource.dart @@ -0,0 +1,55 @@ +import 'package:askaide/helper/logger.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/group.dart'; +import 'package:loading_more_list/loading_more_list.dart'; + +class GroupMessageDatasource extends LoadingMoreBase { + int startId = 0; + bool _hasMore = true; + bool forceRefresh = false; + + final int groupId; + + GroupMessageDatasource({required this.groupId}); + + @override + bool get hasMore => _hasMore || forceRefresh; + + @override + Future loadData([bool isloadMoreAction = false]) async { + try { + final messages = await APIServer() + .chatGroupMessages(groupId, startId: startId, cache: false); + + if (startId == 0) { + clear(); + } + + for (var element in messages.data) { + add(element); + } + + if (messages.data.isEmpty) { + _hasMore = false; + } + + startId = messages.lastId; + return true; + } catch (e) { + Logger.instance.e(e); + return false; + } + } + + @override + Future refresh([bool notifyStateChanged = false]) async { + _hasMore = true; + startId = 1; + //force to refresh list when you don't want clear list before request + //for the case, if your list already has 20 items. + forceRefresh = !notifyStateChanged; + var result = await super.refresh(notifyStateChanged); + forceRefresh = false; + return result; + } +} diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 438869bf..9d736b8f 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -8,12 +8,12 @@ import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; @@ -51,9 +51,11 @@ class _GroupChatPageState extends State { AudioPlayerController(useRemoteAPI: false); bool showAudioPlayer = false; - List selectedMembers = []; + List? selectedMembers = []; List messages = []; + ChatGroup? group; + Timer? timer; @override @@ -103,12 +105,31 @@ class _GroupChatPageState extends State { Widget _buildChatComponents(CustomColors customColors) { return BlocConsumer( - listenWhen: (previous, current) => current is GroupChatLoaded, + listenWhen: (previous, current) => + current is GroupChatLoaded || current is GroupDefaultMemberSelected, listener: (context, state) { if (state is GroupChatLoaded) { // 加载聊天记录列表 context.read().add( GroupChatMessagesLoadEvent(widget.groupId, isInitRequest: true)); + + // 选中默认的聊天成员 + selectedMembers = state.group.members + .where((e) => state.defaultChatMembers?.contains(e.id) ?? false) + .toList(); + + setState(() { + group = state.group; + }); + } + + if (state is GroupDefaultMemberSelected) { + // 选中默认的聊天成员 + if (group != null) { + selectedMembers = group?.members + .where((e) => state.members.contains(e.id)) + .toList(); + } } }, buildWhen: (previous, current) => current is GroupChatLoaded, @@ -160,32 +181,46 @@ class _GroupChatPageState extends State { return [ Stack( children: [ - IconButton( - onPressed: () async { - onModelSelect( - context, groupState, customColors); - }, - icon: const Icon(Icons.group), - color: customColors.chatInputPanelText, - splashRadius: 20, - tooltip: '选择对话的模型', - ), - Positioned( - right: 2, - top: 0, - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 3, vertical: 3), - child: Text( - selectedMembers.isNotEmpty - ? 'x${selectedMembers.length}' - : '随机', - style: TextStyle( - fontSize: 7, - color: customColors.weakTextColor, - )), + Container( + width: 40, + height: 40, + padding: const EdgeInsets.all(5), + child: InkWell( + onTap: () { + onModelSelect(context, groupState, + customColors); + }, + child: selectedMembers?.length == 1 + ? _buildAvatar( + avatarUrl: selectedMembers! + .first.avatarUrl) + : Icon( + Icons.group, + color: customColors + .chatInputPanelText, + ), ), ), + if (selectedMembers?.length != 1) + Positioned( + right: 2, + top: 0, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 3, vertical: 3), + child: Text( + selectedMembers != null && + selectedMembers! + .isNotEmpty + ? 'x${selectedMembers!.length}' + : '随机', + style: TextStyle( + fontSize: 7, + color: + customColors.weakTextColor, + )), + ), + ), ], ) ]; @@ -297,6 +332,7 @@ class _GroupChatPageState extends State { ts: e.createdAt, avatarUrl: member?.avatarUrl, senderName: member?.modelName, + roomId: e.groupId, ); }).toList(); @@ -473,13 +509,10 @@ class _GroupChatPageState extends State { _inputEnabled.value = false; }); - context.read().add( - GroupChatSendEvent( - widget.groupId, - text, - selectedMembers.map((e) => e.id!).toList(), - ), - ); + var replyMemberIds = (selectedMembers ?? []).map((e) => e.id!).toList(); + context + .read() + .add(GroupChatSendEvent(widget.groupId, text, replyMemberIds)); } /// 处理消息删除事件 @@ -548,19 +581,36 @@ class _GroupChatPageState extends State { context, AppLocale.noMessageSelected.getString(context)); return; } - var shareText = messages.map((e) { - if (e.message.role == Role.sender) { - return e.message.text; - } - return e.message.text; - }).join('\n\n'); - - shareTo( + Navigator.push( context, - content: shareText, - title: AppLocale.chatHistory.getString(context), + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => ChatShareScreen( + messages: messages + .map((e) => ChatShareMessage( + content: e.message.text, + username: e.message.senderName, + avatarURL: e.message.avatarUrl, + leftSide: e.message.role == Role.receiver, + )) + .toList(), + ), + ), ); + // var shareText = messages.map((e) { + // if (e.message.role == Role.sender) { + // return '我:\n${e.message.text}'; + // } + + // return '${e.message.senderName ?? "助理"}:\n${e.message.text}'; + // }).join('\n\n'); + + // shareTo( + // context, + // content: shareText, + // title: AppLocale.chatHistory.getString(context), + // ); }, icon: Icon(Icons.share, color: customColors.linkColor), label: Text( diff --git a/lib/repo/api/page.dart b/lib/repo/api/page.dart index 4ebbf57d..e90d3290 100644 --- a/lib/repo/api/page.dart +++ b/lib/repo/api/page.dart @@ -13,3 +13,17 @@ class PagedData { required this.data, }); } + +class OffsetPageData { + int startId; + int lastId; + int perPage; + List data; + + OffsetPageData({ + required this.startId, + required this.lastId, + required this.perPage, + required this.data, + }); +} diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index 55ac5b4c..ffe91df1 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -1634,9 +1634,9 @@ class APIServer { } /// 群组聊天消息列表 - Future> chatGroupMessages( + Future> chatGroupMessages( int groupId, { - int page = 1, + int startId = 0, int? perPage, bool cache = true, }) async { @@ -1648,16 +1648,15 @@ class APIServer { res.add(GroupMessage.fromJson(item)); } - return PagedData( + return OffsetPageData( data: res, - page: resp.data['page'] ?? 1, + lastId: resp.data['last_id'], + startId: resp.data['start_id'], perPage: resp.data['per_page'], - total: resp.data['total'], - lastPage: resp.data['last_page'], ); }, queryParameters: { - 'page': page, + 'start_id': startId, 'per_page': perPage, }, forceRefresh: !cache, diff --git a/lib/repo/model/group.dart b/lib/repo/model/group.dart index 7410a13a..4ac6b7b7 100644 --- a/lib/repo/model/group.dart +++ b/lib/repo/model/group.dart @@ -75,6 +75,7 @@ class GroupMessage { final String message; final String role; final String type; + final int groupId; final int? tokenConsumed; final int? quotaConsumed; final int? pid; @@ -89,6 +90,7 @@ class GroupMessage { required this.role, required this.status, required this.type, + required this.groupId, this.tokenConsumed, this.quotaConsumed, this.pid, @@ -103,6 +105,7 @@ class GroupMessage { message: json['message'] ?? '', role: json['role'] == 1 ? 'user' : 'assistant', type: json['type'] ?? 'text', + groupId: json['group_id'], tokenConsumed: json['token_consumed'], quotaConsumed: json['quota_consumed'], pid: json['pid'], @@ -119,6 +122,7 @@ class GroupMessage { 'message': message, 'role': role, 'type': type, + 'group_id': groupId, 'token_consumed': tokenConsumed, 'quota_consumed': quotaConsumed, 'pid': pid, From 5ef2422067da48019ba65aa1cf1bac20bfce6976 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 25 Oct 2023 12:29:34 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E8=B7=91=E9=80=9A?= =?UTF-8?q?=E5=9F=BA=E4=BA=8E=20WebSocket=20=E6=9B=BF=E6=8D=A2=20OpenAI=20?= =?UTF-8?q?=E7=9A=84=20SSE=20=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helper/ability.dart | 5 ++ lib/repo/api/info.dart | 6 ++ lib/repo/openai_repo.dart | 116 +++++++++++++++++++++++++++++--------- pubspec.lock | 2 +- pubspec.yaml | 1 + 5 files changed, 103 insertions(+), 27 deletions(-) diff --git a/lib/helper/ability.dart b/lib/helper/ability.dart index 1f8e2333..f344efe3 100644 --- a/lib/helper/ability.dart +++ b/lib/helper/ability.dart @@ -20,6 +20,11 @@ class Ability { return _instance; } + /// 是否支持 Websocket + bool supportWebSocket() { + return capabilities.supportWebsocket && !supportLocalOpenAI(); + } + /// 更新能力 updateCapabilities(Capabilities capabilities) { this.capabilities = capabilities; diff --git a/lib/repo/api/info.dart b/lib/repo/api/info.dart index c0f584f8..1d4bf853 100644 --- a/lib/repo/api/info.dart +++ b/lib/repo/api/info.dart @@ -21,6 +21,9 @@ class Capabilities { /// 是否显示首页模型描述 final bool showHomeModelDescription; + /// 是否支持 Websocket + final bool supportWebsocket; + Capabilities({ required this.applePayEnabled, required this.alipayEnabled, @@ -29,6 +32,7 @@ class Capabilities { required this.openaiEnabled, required this.homeModels, this.showHomeModelDescription = true, + this.supportWebsocket = false, }); factory Capabilities.fromJson(Map json) { @@ -42,6 +46,7 @@ class Capabilities { .map((e) => HomeModel.fromJson(e)) .toList(), showHomeModelDescription: json['show_home_model_description'] ?? true, + supportWebsocket: json['support_websocket'] ?? false, ); } @@ -54,6 +59,7 @@ class Capabilities { 'openai_enabled': openaiEnabled, 'home_models': homeModels.map((e) => e.toJson()).toList(), 'show_home_model_description': showHomeModelDescription, + 'support_websocket': supportWebsocket, }; } } diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index 11d84018..e058d0e8 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; @@ -7,6 +8,7 @@ import 'package:askaide/helper/platform.dart'; import 'package:askaide/repo/model/model.dart' as mm; import 'package:dart_openai/openai.dart'; import 'package:askaide/repo/data/settings_data.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; class OpenAIRepository { final SettingDataProvider settings; @@ -245,34 +247,96 @@ class OpenAIRepository { var completer = Completer(); try { - var chatStream = OpenAI.instance.chat.createStream( - model: model, - messages: messages, - temperature: temperature, - user: user, - maxTokens: maxTokens, - n: Ability().supportLocalOpenAI() - ? null - : roomId, // n 参数暂时用不到,复用作为 roomId - ); + if (Ability().supportWebSocket()) { + final serverURL = settings.getDefault(settingServerURL, apiServerURL); + final wsURL = serverURL.startsWith('https://') + ? serverURL.replaceFirst('https://', 'wss://') + : serverURL.replaceFirst('http://', 'ws://'); + + final apiToken = settings.getDefault(settingAPIServerToken, ''); + + final wsUri = Uri.parse( + '$wsURL/v1/chat/completions?ws=true&authorization=$apiToken&client-version=$clientVersion&platform-version=${PlatformTool.operatingSystemVersion()}&platform=${PlatformTool.operatingSystem()}&language=$language'); + + Uri( + scheme: wsUri.scheme, + host: wsUri.host, + port: wsUri.port, + path: wsUri.path, + queryParameters: {}, + ); + var channel = WebSocketChannel.connect(wsUri); + + channel.stream.listen( + (event) { + final evt = jsonDecode(event); + if (evt['code'] != null && evt['code'] > 0) { + throw Exception(event['message']); + } - chatStream.listen( - (event) { - for (var element in event.choices) { - if (element.delta.content != null) { - onData(ChatStreamRespData( - content: element.delta.content!, - role: element.delta.role, - )); + final res = OpenAIStreamChatCompletionModel.fromMap(evt); + for (var element in res.choices) { + if (element.delta.content != null) { + onData(ChatStreamRespData( + content: element.delta.content!, + role: element.delta.role, + )); + } } - } - }, - onDone: () => completer.complete(), - onError: (e) => completer.completeError(e), - cancelOnError: true, - ).onError((e) { - completer.completeError(e); - }); + }, + onDone: () { + channel.sink.close(); + completer.complete(); + }, + onError: (e) { + channel.sink.close(); + completer.completeError(e); + }, + cancelOnError: true, + ).onError((e) { + completer.completeError(e); + }); + + channel.sink.add(jsonEncode({ + 'model': model, + 'messages': messages.map((e) => e.toMap()).toList(), + 'temperature': temperature, + 'user': user, + 'max_tokens': maxTokens, + 'n': Ability().supportLocalOpenAI() + ? null + : roomId, // n 参数暂时用不到,复用作为 roomId + })); + } else { + var chatStream = OpenAI.instance.chat.createStream( + model: model, + messages: messages, + temperature: temperature, + user: user, + maxTokens: maxTokens, + n: Ability().supportLocalOpenAI() + ? null + : roomId, // n 参数暂时用不到,复用作为 roomId + ); + + chatStream.listen( + (event) { + for (var element in event.choices) { + if (element.delta.content != null) { + onData(ChatStreamRespData( + content: element.delta.content!, + role: element.delta.role, + )); + } + } + }, + onDone: () => completer.complete(), + onError: (e) => completer.completeError(e), + cancelOnError: true, + ).onError((e) { + completer.completeError(e); + }); + } } catch (e) { completer.completeError(e); } diff --git a/pubspec.lock b/pubspec.lock index 4f764ccb..c3774ab7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1768,7 +1768,7 @@ packages: source: hosted version: "0.1.4-beta" web_socket_channel: - dependency: transitive + dependency: "direct main" description: name: web_socket_channel sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b diff --git a/pubspec.yaml b/pubspec.yaml index b3ffe1fc..1a57a6dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -114,6 +114,7 @@ dependencies: flutter_markdown: ^0.6.17+3 markdown: ^7.1.1 fetch_api: 1.0.1 + web_socket_channel: ^2.4.0 dev_dependencies: flutter_test: From 26d2563d88d8d374db0fa7b89ee7191a20c7a991 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 25 Oct 2023 12:31:06 +0800 Subject: [PATCH 11/28] update --- lib/repo/openai_repo.dart | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index e058d0e8..a14fcfab 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -255,17 +255,21 @@ class OpenAIRepository { final apiToken = settings.getDefault(settingAPIServerToken, ''); - final wsUri = Uri.parse( - '$wsURL/v1/chat/completions?ws=true&authorization=$apiToken&client-version=$clientVersion&platform-version=${PlatformTool.operatingSystemVersion()}&platform=${PlatformTool.operatingSystem()}&language=$language'); - - Uri( + final wsUri = Uri.parse('$wsURL/v1/chat/completions'); + var channel = WebSocketChannel.connect(Uri( scheme: wsUri.scheme, host: wsUri.host, port: wsUri.port, path: wsUri.path, - queryParameters: {}, - ); - var channel = WebSocketChannel.connect(wsUri); + queryParameters: { + 'ws': 'true', + 'authorization': apiToken, + 'client-version': clientVersion, + 'platform-version': PlatformTool.operatingSystemVersion(), + 'platform': PlatformTool.operatingSystem(), + 'language': language, + }, + )); channel.stream.listen( (event) { From 6fc3d75fbfc17139be7fbf8fcfe78dac0adfbea3 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 25 Oct 2023 16:25:12 +0800 Subject: [PATCH 12/28] =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E6=8E=A7=E5=88=B6=E5=AE=A2=E6=88=B7=E7=AB=AF=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helper/ability.dart | 28 ++++++- lib/main.dart | 7 +- lib/page/app_scaffold.dart | 138 ++++++++++++++++++++------------- lib/page/bind_phone_page.dart | 6 +- lib/page/openai_setting.dart | 5 +- lib/page/signin_or_signup.dart | 5 +- lib/page/signin_screen.dart | 4 +- lib/page/signup_screen.dart | 3 +- lib/repo/api/info.dart | 30 +++++++ 9 files changed, 160 insertions(+), 66 deletions(-) diff --git a/lib/helper/ability.dart b/lib/helper/ability.dart index f344efe3..3e7392c0 100644 --- a/lib/helper/ability.dart +++ b/lib/helper/ability.dart @@ -40,9 +40,35 @@ class Ability { return capabilities.showHomeModelDescription; } + /// 首页路由 + String get homeRoute { + return capabilities.homeRoute; + } + + /// 是否支持绘玩 + bool get enableGallery { + return !capabilities.disableGallery; + } + + /// 是否支持创作岛 + bool get enableCreationIsland { + return !capabilities.disableCreationIsland; + } + + /// 是否支持数字人 + bool get enableDigitalHuman { + return !capabilities.disableDigitalHuman; + } + + /// 是否支持聊一聊 + bool get enableChat { + return !capabilities.disableChat; + } + /// 是否支持 OpenAI bool get enableOpenAI { - return capabilities.openaiEnabled; + return capabilities.openaiEnabled && + (!capabilities.disableChat || !capabilities.disableDigitalHuman); } /// 是否支持支付宝 diff --git a/lib/main.dart b/lib/main.dart index f2b082b8..4dca14fa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -175,7 +175,7 @@ void main() async { // 从服务器获取客户端支持的能力清单 try { - final capabilities = await APIServer().capabilities(); + final capabilities = await APIServer().capabilities(cache: false); Ability().init(settingRepo, capabilities); } catch (e) { Logger.instance.e('获取客户端能力清单失败', error: e); @@ -188,6 +188,9 @@ void main() async { mailEnabled: true, openaiEnabled: true, homeModels: [], + homeRoute: '/chat-chat', + showHomeModelDescription: true, + supportWebsocket: false, ), ); } @@ -264,7 +267,7 @@ class MyApp extends StatefulWidget { !stabilityAISelfHosted; _router = GoRouter( - initialLocation: shouldLogin ? '/login' : '/chat-chat', + initialLocation: shouldLogin ? '/login' : Ability().homeRoute, observers: [ BotToastNavigatorObserver(), ], diff --git a/lib/page/app_scaffold.dart b/lib/page/app_scaffold.dart index f567ae7e..160e65ef 100644 --- a/lib/page/app_scaffold.dart +++ b/lib/page/app_scaffold.dart @@ -1,3 +1,4 @@ +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/event.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/lang/lang.dart'; @@ -44,10 +45,71 @@ class _AppScaffoldState extends State { super.initState(); } + List _bottomNavigationBarList( + {int? currentIndex}) { + return [ + if (Ability().enableChat) + BottomNavigationBarConfig( + builder: (index, customColors) => createAnimatedNavBarItem( + icon: Icons.question_answer_outlined, + activatedIcon: Icons.question_answer, + activatedColor: customColors.linkColor, + label: AppLocale.chatAnywhere.getString(context), + activated: currentIndex == index, + ), + route: '/chat-chat', + ), + if (Ability().enableDigitalHuman) + BottomNavigationBarConfig( + builder: (index, customColors) => createAnimatedNavBarItem( + icon: Icons.group_outlined, + activatedIcon: Icons.group, + activatedColor: customColors.linkColor, + label: AppLocale.homeTitle.getString(context), + activated: currentIndex == index, + ), + route: '/', + ), + if (Ability().enableGallery) + BottomNavigationBarConfig( + builder: (index, customColors) => createAnimatedNavBarItem( + icon: Icons.auto_awesome_outlined, + activatedIcon: Icons.auto_awesome, + activatedColor: customColors.linkColor, + label: AppLocale.discover.getString(context), + activated: currentIndex == index, + ), + route: '/creative-gallery', + ), + if (Ability().enableCreationIsland) + BottomNavigationBarConfig( + builder: (index, customColors) => createAnimatedNavBarItem( + icon: Icons.palette_outlined, + activatedIcon: Icons.palette, + activatedColor: customColors.linkColor, + label: AppLocale.creativeIsland.getString(context), + activated: currentIndex == index, + ), + route: '/creative-draw', + ), + BottomNavigationBarConfig( + builder: (index, customColors) => createAnimatedNavBarItem( + icon: Icons.manage_accounts_outlined, + activatedIcon: Icons.manage_accounts, + activatedColor: customColors.linkColor, + label: AppLocale.me.getString(context), + activated: currentIndex == index, + ), + route: '/setting', + ), + ]; + } + @override Widget build(BuildContext context) { final currentIndex = _calculateSelectedIndex(context); final customColors = Theme.of(context).extension()!; + final barItems = _bottomNavigationBarList(currentIndex: currentIndex); return Scaffold( backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( @@ -72,41 +134,8 @@ class _AppScaffoldState extends State { backgroundColor: customColors.backgroundColor, elevation: 0, items: [ - createAnimatedNavBarItem( - icon: Icons.question_answer_outlined, - activatedIcon: Icons.question_answer, - activatedColor: customColors.linkColor, - label: AppLocale.chatAnywhere.getString(context), - activated: currentIndex == 0, - ), - createAnimatedNavBarItem( - icon: Icons.group_outlined, - activatedIcon: Icons.group, - activatedColor: customColors.linkColor, - label: AppLocale.homeTitle.getString(context), - activated: currentIndex == 1, - ), - createAnimatedNavBarItem( - icon: Icons.auto_awesome_outlined, - activatedIcon: Icons.auto_awesome, - activatedColor: customColors.linkColor, - label: AppLocale.discover.getString(context), - activated: currentIndex == 2, - ), - createAnimatedNavBarItem( - icon: Icons.palette_outlined, - activatedIcon: Icons.palette, - activatedColor: customColors.linkColor, - label: AppLocale.creativeIsland.getString(context), - activated: currentIndex == 3, - ), - createAnimatedNavBarItem( - icon: Icons.manage_accounts_outlined, - activatedIcon: Icons.manage_accounts, - activatedColor: customColors.linkColor, - label: AppLocale.me.getString(context), - activated: currentIndex == 4, - ), + for (var i = 0; i < barItems.length; i++) + barItems[i].builder(i, customColors), ], ) : null, @@ -117,31 +146,21 @@ class _AppScaffoldState extends State { final GoRouter route = GoRouter.of(context); final String location = route.location.split('?').first; - if (location == '/chat-chat') return 0; - if (location == '/') return 1; - if (location == '/creative-gallery') return 2; - if (location == '/creative-draw') return 3; - if (location == '/setting') return 4; + final barItems = _bottomNavigationBarList(); + for (var i = 0; i < barItems.length; i++) { + if (barItems[i].route == location) return i; + } return -1; } void onTap(int value) { HapticFeedbackHelper.lightImpact(); - switch (value) { - case 0: - return context.go('/chat-chat'); - case 1: - return context.go('/'); - case 2: - return context.go('/creative-gallery'); - case 3: - return context.go('/creative-draw'); - case 4: - return context.go('/setting'); - default: - return context.go('/'); - } + + final barItems = _bottomNavigationBarList(); + if (value >= barItems.length) return context.go(Ability().homeRoute); + + return context.go(barItems[value].route); } } @@ -163,3 +182,14 @@ BottomNavigationBarItem createAnimatedNavBarItem({ ), ); } + +class BottomNavigationBarConfig { + final BottomNavigationBarItem Function(int index, CustomColors customColors) + builder; + final String route; + + BottomNavigationBarConfig({ + required this.builder, + required this.route, + }); +} diff --git a/lib/page/bind_phone_page.dart b/lib/page/bind_phone_page.dart index 65a7e338..94fcb265 100644 --- a/lib/page/bind_phone_page.dart +++ b/lib/page/bind_phone_page.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:askaide/bloc/account_bloc.dart'; +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; @@ -86,7 +87,8 @@ class _BindPhoneScreenState extends State { leading: IconButton( onPressed: () { if (widget.isSignIn) { - context.go('/chat-chat?show_initial_dialog=false&reward=0'); + context.go( + '${Ability().homeRoute}?show_initial_dialog=false&reward=0'); } else { context.pop(); } @@ -292,7 +294,7 @@ class _BindPhoneScreenState extends State { if (widget.isSignIn) { if (context.mounted) { context.go( - '/chat-chat?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); + '${Ability().homeRoute}?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); } } else { if (context.mounted) { diff --git a/lib/page/openai_setting.dart b/lib/page/openai_setting.dart index c245504f..743b40aa 100644 --- a/lib/page/openai_setting.dart +++ b/lib/page/openai_setting.dart @@ -1,3 +1,4 @@ +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; @@ -78,7 +79,7 @@ class _OpenAISettingScreenState extends State { if (widget.source != 'setting') TextButton( onPressed: () { - context.go('/chat-chat'); + context.go(Ability().homeRoute); }, child: Text( '暂不设置', @@ -227,7 +228,7 @@ class _OpenAISettingScreenState extends State { } else { await widget.settings.set(settingOpenAISelfHosted, 'true'); if (context.mounted) { - context.go('/chat-chat'); + context.go(Ability().homeRoute); } } } diff --git a/lib/page/signin_or_signup.dart b/lib/page/signin_or_signup.dart index 58c18dd9..be9c3c78 100644 --- a/lib/page/signin_or_signup.dart +++ b/lib/page/signin_or_signup.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; @@ -164,7 +165,7 @@ class _SigninOrSignupScreenState extends State { await widget.settings.set(settingUserInfo, jsonEncode(value)); if (context.mounted) { context.go( - '/chat-chat?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); + '${Ability().homeRoute}?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); } }).catchError((e) { showErrorMessage(resolveError(context, e)); @@ -405,7 +406,7 @@ class _SigninOrSignupScreenState extends State { } else { if (context.mounted) { context.go( - '/chat-chat?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); + '${Ability().homeRoute}?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); } } }).catchError((e) { diff --git a/lib/page/signin_screen.dart b/lib/page/signin_screen.dart index 60fcd176..93dead5c 100644 --- a/lib/page/signin_screen.dart +++ b/lib/page/signin_screen.dart @@ -77,7 +77,7 @@ class _SignInScreenState extends State { if (context.canPop()) { context.pop(); } else { - context.go('/chat-chat'); + context.go(Ability().homeRoute); } }, ), @@ -410,7 +410,7 @@ class _SignInScreenState extends State { return; } else { context.go( - '/chat-chat?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); + '${Ability().homeRoute}?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); } }); }).catchError((e) { diff --git a/lib/page/signup_screen.dart b/lib/page/signup_screen.dart index 23396423..851488ae 100644 --- a/lib/page/signup_screen.dart +++ b/lib/page/signup_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:animated_text_kit/animated_text_kit.dart'; +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/env.dart'; import 'package:askaide/helper/helper.dart'; @@ -558,7 +559,7 @@ class _SignupScreenState extends State { } else { if (context.mounted) { context.go( - '/chat-chat?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); + '${Ability().homeRoute}?show_initial_dialog=${value.isNewUser ? "true" : "false"}&reward=${value.reward}'); } } }).catchError((e) { diff --git a/lib/repo/api/info.dart b/lib/repo/api/info.dart index 1d4bf853..33aba414 100644 --- a/lib/repo/api/info.dart +++ b/lib/repo/api/info.dart @@ -21,9 +21,24 @@ class Capabilities { /// 是否显示首页模型描述 final bool showHomeModelDescription; + /// 首页路由 + final String homeRoute; + /// 是否支持 Websocket final bool supportWebsocket; + /// 是否显示绘玩 + final bool disableGallery; + + /// 是否支持创作岛 + final bool disableCreationIsland; + + /// 是否禁用数字人 + final bool disableDigitalHuman; + + /// 是否禁用聊天 + final bool disableChat; + Capabilities({ required this.applePayEnabled, required this.alipayEnabled, @@ -31,8 +46,13 @@ class Capabilities { required this.mailEnabled, required this.openaiEnabled, required this.homeModels, + this.homeRoute = '/chat-chat', this.showHomeModelDescription = true, this.supportWebsocket = false, + this.disableGallery = false, + this.disableCreationIsland = false, + this.disableDigitalHuman = false, + this.disableChat = false, }); factory Capabilities.fromJson(Map json) { @@ -45,8 +65,13 @@ class Capabilities { homeModels: ((json['home_models'] ?? []) as List) .map((e) => HomeModel.fromJson(e)) .toList(), + homeRoute: json['home_route'] ?? '/chat-chat', showHomeModelDescription: json['show_home_model_description'] ?? true, supportWebsocket: json['support_websocket'] ?? false, + disableGallery: json['disable_gallery'] ?? false, + disableCreationIsland: json['disable_creation_island'] ?? false, + disableDigitalHuman: json['disable_digital_human'] ?? false, + disableChat: json['disable_chat'] ?? false, ); } @@ -58,8 +83,13 @@ class Capabilities { 'mail_enabled': mailEnabled, 'openai_enabled': openaiEnabled, 'home_models': homeModels.map((e) => e.toJson()).toList(), + 'home_route': homeRoute, 'show_home_model_description': showHomeModelDescription, 'support_websocket': supportWebsocket, + 'disable_gallery': disableGallery, + 'disable_creation_island': disableCreationIsland, + 'disable_digital_human': disableDigitalHuman, + 'disable_chat': disableChat, }; } } From 9874fb058580037563cee763b20ca5ccce2c2c42 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Wed, 25 Oct 2023 18:04:22 +0800 Subject: [PATCH 13/28] =?UTF-8?q?bugfix:=20Websocket=20=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E5=BC=82=E5=B8=B8=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/chat_message_bloc.dart | 13 +++++++++++++ lib/repo/openai_repo.dart | 12 +++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/bloc/chat_message_bloc.dart b/lib/bloc/chat_message_bloc.dart index 9470d7c5..69607d91 100644 --- a/lib/bloc/chat_message_bloc.dart +++ b/lib/bloc/chat_message_bloc.dart @@ -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'; @@ -305,6 +306,7 @@ class ChatMessageBloc extends BlocExt { // 等待监听机器人应答消息 final queue = GracefulQueue(); try { + RequestFailedException? error; var listener = queue.listen(const Duration(milliseconds: 10), (items) { final systemCmds = items.where((e) => e.role == 'system').toList(); if (systemCmds.isNotEmpty) { @@ -335,6 +337,13 @@ class ChatMessageBloc extends BlocExt { .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 @@ -348,6 +357,10 @@ class ChatMessageBloc extends BlocExt { await listener; + if (error != null) { + throw error!; + } + // 机器人应答完成,将最后一条机器人应答消息更新到数据库,替换掉思考中消息 waitMessage.isReady = true; await chatMsgRepo.updateMessage(roomId, waitMessage.id!, waitMessage); diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index a14fcfab..b5b17a49 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -275,7 +275,13 @@ class OpenAIRepository { (event) { final evt = jsonDecode(event); if (evt['code'] != null && evt['code'] > 0) { - throw Exception(event['message']); + onData(ChatStreamRespData( + content: evt['error'], + code: evt['code'], + error: evt['error'], + )); + + return; } final res = OpenAIStreamChatCompletionModel.fromMap(evt); @@ -378,9 +384,13 @@ class ChatReplyMessage { class ChatStreamRespData { final String? role; final String content; + final int? code; + final String? error; ChatStreamRespData({ this.role, required this.content, + this.code, + this.error, }); } From 5e59bd280a51150f79de4133112e557730774142 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 00:16:31 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E5=88=86=E4=BA=AB=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/chat_chat_bloc.dart | 2 +- lib/page/chat_screen.dart | 47 +++++++++++++++++++------ lib/page/component/chat/chat_share.dart | 8 ++--- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index 17d2a019..b0418994 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -16,7 +16,7 @@ class ChatChatBloc extends Bloc { on((event, emit) async { final histories = await _chatMessageRepository.recentChatHistories( chatAnywhereRoomId, - 10, + 3, userId: APIServer().localUserID(), ); diff --git a/lib/page/chat_screen.dart b/lib/page/chat_screen.dart index b349b6cb..47b19867 100644 --- a/lib/page/chat_screen.dart +++ b/lib/page/chat_screen.dart @@ -6,6 +6,7 @@ import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/empty.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; @@ -247,6 +248,9 @@ class _ChatScreenState extends State { } final messages = loadedMessages.map((e) { + e.avatarUrl = room.room.avatarUrl; + e.senderName = room.room.name; + return MessageWithState( e, room.states[ @@ -583,19 +587,42 @@ Widget buildSelectModeToolbars( context, AppLocale.noMessageSelected.getString(context)); return; } - var shareText = messages.map((e) { - if (e.message.role == Role.sender) { - return '我:\n${e.message.text}'; - } - - return '助理:\n${e.message.text}'; - }).join('\n\n'); - shareTo( + Navigator.push( context, - content: shareText, - title: AppLocale.chatHistory.getString(context), + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => ChatShareScreen( + messages: messages + .map((e) => ChatShareMessage( + content: e.message.text, + username: e.message.senderName, + avatarURL: e.message.avatarUrl, + leftSide: e.message.role == Role.receiver, + )) + .toList(), + ), + ), ); + // var messages = chatPreviewController.selectedMessages(); + // if (messages.isEmpty) { + // showErrorMessageEnhanced( + // context, AppLocale.noMessageSelected.getString(context)); + // return; + // } + // var shareText = messages.map((e) { + // if (e.message.role == Role.sender) { + // return '我:\n${e.message.text}'; + // } + + // return '助理:\n${e.message.text}'; + // }).join('\n\n'); + + // shareTo( + // context, + // content: shareText, + // title: AppLocale.chatHistory.getString(context), + // ); }, icon: Icon(Icons.share, color: customColors.linkColor), label: Text( diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index af7caa08..381928c4 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -277,9 +277,9 @@ class _ChatShareScreenState extends State { children: [ Row( children: [ - if (message.avatarURL != null) + if (message.avatarURL != null && message.leftSide) _buildAvatar(avatarUrl: message.avatarURL), - if (message.username != null) + if (message.username != null && message.leftSide) Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), padding: const EdgeInsets.symmetric(horizontal: 13), @@ -348,7 +348,7 @@ class _ChatShareScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (message.avatarURL != null) + if (message.avatarURL != null && message.leftSide) _buildAvatar(avatarUrl: message.avatarURL), ConstrainedBox( constraints: BoxConstraints( @@ -358,7 +358,7 @@ class _ChatShareScreenState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (message.username != null) + if (message.username != null && message.leftSide) Container( margin: const EdgeInsets.fromLTRB(0, 0, 10, 7), padding: From 070c0ac521d4427044a45fe5deca3f30184ead0d Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 01:50:26 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E6=A0=B7=E5=BC=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/chat_chat_bloc.dart | 2 +- lib/page/app_scaffold.dart | 4 ++ lib/page/chat_chat.dart | 77 +++++++++++++++++++-- lib/page/chat_room_create.dart | 12 ---- lib/page/component/enhanced_popup_menu.dart | 5 +- lib/page/group/chat.dart | 28 +++----- lib/page/group/create.dart | 2 +- lib/page/group/edit.dart | 2 +- lib/page/home_screen.dart | 35 ++++++++-- lib/page/rooms.dart | 2 +- 10 files changed, 122 insertions(+), 47 deletions(-) diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index b0418994..c21f95b3 100644 --- a/lib/bloc/chat_chat_bloc.dart +++ b/lib/bloc/chat_chat_bloc.dart @@ -16,7 +16,7 @@ class ChatChatBloc extends Bloc { on((event, emit) async { final histories = await _chatMessageRepository.recentChatHistories( chatAnywhereRoomId, - 3, + 4, userId: APIServer().localUserID(), ); diff --git a/lib/page/app_scaffold.dart b/lib/page/app_scaffold.dart index 160e65ef..457a869e 100644 --- a/lib/page/app_scaffold.dart +++ b/lib/page/app_scaffold.dart @@ -155,6 +155,10 @@ class _AppScaffoldState extends State { } void onTap(int value) { + if (context.canPop()) { + context.pop(); + } + HapticFeedbackHelper.lightImpact(); final barItems = _bottomNavigationBarList(); diff --git a/lib/page/chat_chat.dart b/lib/page/chat_chat.dart index 83dfec88..92ff1f7f 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat_chat.dart @@ -25,6 +25,7 @@ import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; @@ -90,6 +91,21 @@ class _ChatChatScreenState extends State { /// 促销事件 PromotionEvent? promotionEvent; + /// 用于监听键盘事件,实现回车发送消息,Shift+Enter换行 + late final FocusNode _focusNode = FocusNode( + onKey: (node, event) { + if (!event.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { + if (event is RawKeyDownEvent) { + onSubmit(context, _textController.text.trim()); + } + + return KeyEventResult.handled; + } else { + return KeyEventResult.ignored; + } + }, + ); + @override void dispose() { _textController.dispose(); @@ -279,7 +295,7 @@ class _ChatChatScreenState extends State { margin: const EdgeInsets.only(top: 10, left: 15), child: Text( - '最近历史记录', + AppLocale.histories.getString(context), style: TextStyle( color: customColors.weakTextColor ?.withAlpha(100), @@ -289,6 +305,56 @@ class _ChatChatScreenState extends State { ), ); } + + if (index == state.histories.length && index > 3) { + return SafeArea( + top: false, + bottom: false, + child: GestureDetector( + onTap: () { + context + .push('/chat-chat/history') + .whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); + }); + }, + child: Container( + alignment: Alignment.center, + margin: const EdgeInsets.only( + top: 5, bottom: 15), + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon( + Icons.keyboard_double_arrow_left, + size: 12, + color: customColors.weakTextColor! + .withAlpha(120), + ), + Text( + "查看更多", + style: TextStyle( + fontSize: 12, + color: customColors.weakTextColor! + .withAlpha(120), + ), + ), + Icon( + Icons.keyboard_double_arrow_right, + size: 12, + color: customColors.weakTextColor! + .withAlpha(120), + ), + ], + ), + ), + ), + ); + } + return SafeArea( top: false, bottom: false, @@ -417,6 +483,7 @@ class _ChatChatScreenState extends State { ), Expanded( child: EnhancedTextField( + focusNode: _focusNode, controller: _textController, customColors: customColors, maxLines: 10, @@ -692,10 +759,6 @@ class _ChatChatScreenState extends State { ), InkWell( onTap: () { - if (_textController.text.trim().isEmpty) { - return; - } - onSubmit(context, _textController.text.trim()); }, child: Icon( @@ -712,6 +775,10 @@ class _ChatChatScreenState extends State { } void onSubmit(BuildContext context, String text) { + if (text.trim().isEmpty) { + return; + } + context .push(Uri(path: '/chat-anywhere', queryParameters: { 'init_message': text, diff --git a/lib/page/chat_room_create.dart b/lib/page/chat_room_create.dart index dbeaeb7d..b67d432f 100644 --- a/lib/page/chat_room_create.dart +++ b/lib/page/chat_room_create.dart @@ -105,18 +105,6 @@ class _ChatRoomCreateScreenState extends State { backgroundColor: customColors.backgroundContainerColor, centerTitle: true, toolbarHeight: CustomSize.toolbarHeight, - actions: [ - if (Ability().supportAPIServer() && !Ability().supportLocalOpenAI()) - IconButton( - icon: const Icon(Icons.group_add), - onPressed: () { - context.push('/group-chat-create').whenComplete(() { - context.read().add(RoomsLoadEvent()); - }); - }, - ), - const SizedBox(width: 8), - ], ), body: BackgroundContainer( setting: widget.setting, diff --git a/lib/page/component/enhanced_popup_menu.dart b/lib/page/component/enhanced_popup_menu.dart index a52f2ad5..a2943f37 100644 --- a/lib/page/component/enhanced_popup_menu.dart +++ b/lib/page/component/enhanced_popup_menu.dart @@ -16,12 +16,13 @@ class EnhancedPopupMenuItem { class EnhancedPopupMenu extends StatelessWidget { final List items; - const EnhancedPopupMenu({super.key, required this.items}); + final IconData? icon; + const EnhancedPopupMenu({super.key, required this.items, this.icon}); @override Widget build(BuildContext context) { return PopupMenuButton( - icon: const Icon(Icons.more_horiz), + icon: Icon(icon ?? Icons.more_horiz), splashRadius: 20, elevation: 0, shape: RoundedRectangleBorder( diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 9d736b8f..0c3df229 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -190,18 +190,17 @@ class _GroupChatPageState extends State { onModelSelect(context, groupState, customColors); }, - child: selectedMembers?.length == 1 - ? _buildAvatar( - avatarUrl: selectedMembers! - .first.avatarUrl) - : Icon( - Icons.group, - color: customColors - .chatInputPanelText, - ), + child: Icon( + Icons.alternate_email, + color: selectedMembers != null && + selectedMembers!.isNotEmpty + ? customColors.linkColor + : customColors.chatInputPanelText, + ), ), ), - if (selectedMembers?.length != 1) + if (selectedMembers != null && + selectedMembers!.isNotEmpty) Positioned( right: 2, top: 0, @@ -209,15 +208,10 @@ class _GroupChatPageState extends State { padding: const EdgeInsets.symmetric( horizontal: 3, vertical: 3), child: Text( - selectedMembers != null && - selectedMembers! - .isNotEmpty - ? 'x${selectedMembers!.length}' - : '随机', + 'x${selectedMembers!.length}', style: TextStyle( fontSize: 7, - color: - customColors.weakTextColor, + color: customColors.linkColor, )), ), ), diff --git a/lib/page/group/create.dart b/lib/page/group/create.dart index 60ecd89d..3ae3d4e0 100644 --- a/lib/page/group/create.dart +++ b/lib/page/group/create.dart @@ -81,7 +81,7 @@ class _GroupCreatePageState extends State { return Scaffold( appBar: AppBar( title: const Text( - '创建群组', + '发起群聊', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, diff --git a/lib/page/group/edit.dart b/lib/page/group/edit.dart index 505cf20f..8e7a9b2a 100644 --- a/lib/page/group/edit.dart +++ b/lib/page/group/edit.dart @@ -105,7 +105,7 @@ class _GroupEditPageState extends State { return Scaffold( appBar: AppBar( title: const Text( - '群组设置', + '群聊设置', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, diff --git a/lib/page/home_screen.dart b/lib/page/home_screen.dart index 97ae5ccc..9837a8e1 100644 --- a/lib/page/home_screen.dart +++ b/lib/page/home_screen.dart @@ -1,9 +1,11 @@ +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/event.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/enhanced_error.dart'; +import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/room_card.dart'; import 'package:askaide/page/component/sliver_component.dart'; @@ -69,13 +71,32 @@ class _CharactersScreenState extends State { return SliverComponent( actions: [ if (selectedSuggestions.isEmpty) - IconButton( - onPressed: () { - context.push('/create-room').whenComplete(() { - context.read().add(RoomsLoadEvent()); - }); - }, - icon: const Icon(Icons.add_circle_outline), + EnhancedPopupMenu( + items: [ + EnhancedPopupMenuItem( + title: '创建数字人', + icon: Icons.person_add_alt_outlined, + onTap: (p0) { + context.push('/create-room').whenComplete(() { + context.read().add(RoomsLoadEvent()); + }); + }, + ), + if (Ability().supportAPIServer() && + !Ability().supportLocalOpenAI()) + EnhancedPopupMenuItem( + title: '发起群聊', + icon: Icons.chat_bubble_outline, + onTap: (p0) { + context + .push('/group-chat-create') + .whenComplete(() { + context.read().add(RoomsLoadEvent()); + }); + }, + ) + ], + icon: Icons.add_circle_outline, ), ], centerTitle: state.suggests.isEmpty, diff --git a/lib/page/rooms.dart b/lib/page/rooms.dart index a8a2ae27..50778e30 100644 --- a/lib/page/rooms.dart +++ b/lib/page/rooms.dart @@ -151,7 +151,7 @@ class RoomItem extends StatelessWidget { padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 2), child: Text( - '群组', + '群聊', style: TextStyle( color: customColors.weakTextColor, fontSize: 8, From d05ca67098c714ed12ec923f4c58a92ff0a7ba4f Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 10:08:45 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 67 +++++++++---------- lib/page/{ => auth}/signin_or_signup.dart | 2 +- lib/page/{ => auth}/signin_screen.dart | 2 +- lib/page/{ => auth}/signup_screen.dart | 2 +- lib/page/{ => balance}/free_statistics.dart | 2 +- lib/page/{ => balance}/payment.dart | 2 +- .../{ => balance}/quota_detail_screen.dart | 0 .../{ => balance}/quota_usage_statistics.dart | 0 .../component/room_item.dart} | 2 +- lib/page/{chat_chat.dart => chat/home.dart} | 10 +-- .../home_chat.dart} | 12 ++-- .../home_chat_history.dart} | 10 +-- .../{chat_screen.dart => chat/room_chat.dart} | 11 ++- .../room_create.dart} | 10 +-- .../room_edit.dart} | 13 ++-- .../{home_screen.dart => chat/rooms.dart} | 12 ++-- lib/page/component/chat/chat_input.dart | 2 +- lib/page/component/chat/chat_preview.dart | 2 +- lib/page/component/chat/chat_share.dart | 2 +- lib/page/component/chat/voice_record.dart | 2 +- lib/page/{ => component}/dialog.dart | 0 lib/page/component/gallery_item_share.dart | 2 +- lib/page/component/image_preview.dart | 2 +- lib/page/component/model_item.dart | 2 +- lib/page/component/room_card.dart | 2 +- lib/page/component/share.dart | 2 +- lib/page/component/verify_code_input.dart | 2 +- .../creative_island_create_page.dart | 2 +- .../creative_island_gallery.dart | 2 +- .../creative_island_history.dart | 2 +- .../creative_island_history_all.dart | 2 +- .../creative_island_history_preview.dart | 2 +- .../creative_island_result.dart | 2 +- .../draw/components/image_style_selector.dart | 2 +- lib/page/draw/draw_create.dart | 2 +- lib/page/draw/image_edit_direct.dart | 2 +- lib/page/gallery/gallery_item.dart | 2 +- lib/page/group/chat.dart | 2 +- lib/page/group/create.dart | 2 +- lib/page/group/edit.dart | 2 +- lib/page/{ => lab}/avatar_selector.dart | 0 lib/page/lab/creative_models.dart | 2 +- lib/page/lab/draw_board.dart | 2 +- lib/page/{ => lab}/prompt.dart | 2 +- lib/page/lab/user_center.dart | 2 +- lib/page/{ => setting}/account_security.dart | 2 +- .../{ => setting}/background_selector.dart | 2 +- lib/page/{ => setting}/bind_phone_page.dart | 2 +- lib/page/{ => setting}/change_password.dart | 2 +- .../{ => setting}/custom_home_models.dart | 4 +- lib/page/{ => setting}/destroy_account.dart | 2 +- lib/page/{ => setting}/diagnosis.dart | 2 +- lib/page/{ => setting}/openai_setting.dart | 2 +- .../retrieve_password_screen.dart | 2 +- lib/page/{ => setting}/setting_screen.dart | 4 +- 55 files changed, 116 insertions(+), 119 deletions(-) rename lib/page/{ => auth}/signin_or_signup.dart (99%) rename lib/page/{ => auth}/signin_screen.dart (99%) rename lib/page/{ => auth}/signup_screen.dart (99%) rename lib/page/{ => balance}/free_statistics.dart (99%) rename lib/page/{ => balance}/payment.dart (99%) rename lib/page/{ => balance}/quota_detail_screen.dart (100%) rename lib/page/{ => balance}/quota_usage_statistics.dart (100%) rename lib/page/{rooms.dart => chat/component/room_item.dart} (99%) rename lib/page/{chat_chat.dart => chat/home.dart} (99%) rename lib/page/{chat_anywhere.dart => chat/home_chat.dart} (97%) rename lib/page/{chat_history.dart => chat/home_chat_history.dart} (94%) rename lib/page/{chat_screen.dart => chat/room_chat.dart} (99%) rename lib/page/{chat_room_create.dart => chat/room_create.dart} (98%) rename lib/page/{chat_room_setting.dart => chat/room_edit.dart} (98%) rename lib/page/{home_screen.dart => chat/rooms.dart} (97%) rename lib/page/{ => component}/dialog.dart (100%) rename lib/page/{ => lab}/avatar_selector.dart (100%) rename lib/page/{ => lab}/prompt.dart (98%) rename lib/page/{ => setting}/account_security.dart (99%) rename lib/page/{ => setting}/background_selector.dart (99%) rename lib/page/{ => setting}/bind_phone_page.dart (99%) rename lib/page/{ => setting}/change_password.dart (98%) rename lib/page/{ => setting}/custom_home_models.dart (98%) rename lib/page/{ => setting}/destroy_account.dart (98%) rename lib/page/{ => setting}/diagnosis.dart (98%) rename lib/page/{ => setting}/openai_setting.dart (99%) rename lib/page/{ => setting}/retrieve_password_screen.dart (99%) rename lib/page/{ => setting}/setting_screen.dart (99%) diff --git a/lib/main.dart b/lib/main.dart index 4dca14fa..a1cd2e8b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,16 +15,16 @@ import 'package:askaide/helper/model_resolver.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/data/migrate.dart'; -import 'package:askaide/page/account_security.dart'; +import 'package:askaide/page/setting/account_security.dart'; import 'package:askaide/page/app_scaffold.dart'; -import 'package:askaide/page/avatar_selector.dart'; -import 'package:askaide/page/background_selector.dart'; -import 'package:askaide/page/bind_phone_page.dart'; -import 'package:askaide/page/change_password.dart'; -import 'package:askaide/page/chat_anywhere.dart'; -import 'package:askaide/page/chat_chat.dart'; -import 'package:askaide/page/chat_history.dart'; -import 'package:askaide/page/chat_room_create.dart'; +import 'package:askaide/page/lab/avatar_selector.dart'; +import 'package:askaide/page/setting/background_selector.dart'; +import 'package:askaide/page/setting/bind_phone_page.dart'; +import 'package:askaide/page/setting/change_password.dart'; +import 'package:askaide/page/chat/home_chat.dart'; +import 'package:askaide/page/chat/home.dart'; +import 'package:askaide/page/chat/home_chat_history.dart'; +import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/transition_resolver.dart'; import 'package:askaide/page/creative_island/creative_island.dart'; @@ -33,30 +33,30 @@ import 'package:askaide/page/creative_island/creative_island_gallery.dart'; import 'package:askaide/page/creative_island/creative_island_history.dart'; import 'package:askaide/page/creative_island/creative_island_history_all.dart'; import 'package:askaide/page/creative_island/creative_island_history_preview.dart'; -import 'package:askaide/page/custom_home_models.dart'; -import 'package:askaide/page/free_statistics.dart'; +import 'package:askaide/page/setting/custom_home_models.dart'; +import 'package:askaide/page/balance/free_statistics.dart'; import 'package:askaide/page/group/chat.dart'; import 'package:askaide/page/group/create.dart'; import 'package:askaide/page/group/edit.dart'; import 'package:askaide/page/lab/creative_models.dart'; -import 'package:askaide/page/destroy_account.dart'; -import 'package:askaide/page/diagnosis.dart'; +import 'package:askaide/page/setting/destroy_account.dart'; +import 'package:askaide/page/setting/diagnosis.dart'; import 'package:askaide/page/draw/draw.dart'; import 'package:askaide/page/draw/draw_create.dart'; import 'package:askaide/page/draw/image_edit_direct.dart'; import 'package:askaide/page/lab/draw_board.dart'; import 'package:askaide/page/gallery/gallery.dart'; import 'package:askaide/page/gallery/gallery_item.dart'; -import 'package:askaide/page/openai_setting.dart'; -import 'package:askaide/page/payment.dart'; -import 'package:askaide/page/prompt.dart'; -import 'package:askaide/page/quota_usage_statistics.dart'; -import 'package:askaide/page/signin_or_signup.dart'; -import 'package:askaide/page/signin_screen.dart'; +import 'package:askaide/page/setting/openai_setting.dart'; +import 'package:askaide/page/balance/payment.dart'; +import 'package:askaide/page/lab/prompt.dart'; +import 'package:askaide/page/balance/quota_usage_statistics.dart'; +import 'package:askaide/page/auth/signin_or_signup.dart'; +import 'package:askaide/page/auth/signin_screen.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; -import 'package:askaide/page/quota_detail_screen.dart'; -import 'package:askaide/page/retrieve_password_screen.dart'; -import 'package:askaide/page/signup_screen.dart'; +import 'package:askaide/page/balance/quota_detail_screen.dart'; +import 'package:askaide/page/setting/retrieve_password_screen.dart'; +import 'package:askaide/page/auth/signup_screen.dart'; import 'package:askaide/page/lab/user_center.dart'; import 'package:askaide/repo/api/info.dart'; import 'package:askaide/repo/api_server.dart'; @@ -79,10 +79,10 @@ import 'package:askaide/bloc/bloc_manager.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; -import 'package:askaide/page/chat_room_setting.dart'; -import 'package:askaide/page/chat_screen.dart'; -import 'package:askaide/page/home_screen.dart'; -import 'package:askaide/page/setting_screen.dart'; +import 'package:askaide/page/chat/room_edit.dart'; +import 'package:askaide/page/chat/room_chat.dart'; +import 'package:askaide/page/chat/rooms.dart'; +import 'package:askaide/page/setting/setting_screen.dart'; import 'package:askaide/repo/data/chat_message_data.dart'; import 'package:askaide/repo/chat_message_repo.dart'; import 'package:askaide/repo/data/room_data.dart'; @@ -359,7 +359,7 @@ class MyApp extends StatefulWidget { BlocProvider(create: (context) => NotifyBloc()), BlocProvider.value(value: freeCountBloc), ], - child: ChatAnywhereScreen( + child: HomeChatPage( stateManager: messageStateManager, setting: settingRepo, chatId: @@ -385,7 +385,7 @@ class MyApp extends StatefulWidget { create: (context) => ChatChatBloc(chatMsgRepo)), BlocProvider.value(value: freeCountBloc), ], - child: ChatChatScreen( + child: HomePage( setting: settingRepo, showInitialDialog: state.queryParameters['show_initial_dialog'] == 'true', @@ -404,7 +404,7 @@ class MyApp extends StatefulWidget { BlocProvider( create: (context) => ChatChatBloc(chatMsgRepo)), ], - child: ChatHistoryPage( + child: HomeChatHistoryPage( setting: settingRepo, chatMessageRepo: chatMsgRepo, ), @@ -429,7 +429,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [BlocProvider.value(value: chatRoomBloc)], - child: CharactersScreen(setting: settingRepo), + child: RoomsPage(setting: settingRepo), ), ), ), @@ -439,7 +439,7 @@ class MyApp extends StatefulWidget { pageBuilder: (context, state) => transitionResolver( MultiBlocProvider( providers: [BlocProvider.value(value: chatRoomBloc)], - child: ChatRoomCreateScreen(setting: settingRepo), + child: RoomCreatePage(setting: settingRepo), ), ), ), @@ -458,7 +458,7 @@ class MyApp extends StatefulWidget { BlocProvider(create: (context) => NotifyBloc()), BlocProvider.value(value: freeCountBloc), ], - child: ChatScreen( + child: RoomChatPage( roomId: roomId, stateManager: messageStateManager, setting: settingRepo, @@ -480,8 +480,7 @@ class MyApp extends StatefulWidget { value: ChatBlocManager().getBloc(roomId), ), ], - child: ChatRoomSettingScreen( - roomId: roomId, setting: settingRepo), + child: RoomEditPage(roomId: roomId, setting: settingRepo), ), ); }, diff --git a/lib/page/signin_or_signup.dart b/lib/page/auth/signin_or_signup.dart similarity index 99% rename from lib/page/signin_or_signup.dart rename to lib/page/auth/signin_or_signup.dart index be9c3c78..c8811a9e 100644 --- a/lib/page/signin_or_signup.dart +++ b/lib/page/auth/signin_or_signup.dart @@ -8,7 +8,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/signin_screen.dart b/lib/page/auth/signin_screen.dart similarity index 99% rename from lib/page/signin_screen.dart rename to lib/page/auth/signin_screen.dart index 93dead5c..16a81d48 100644 --- a/lib/page/signin_screen.dart +++ b/lib/page/auth/signin_screen.dart @@ -10,7 +10,7 @@ import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/signup_screen.dart b/lib/page/auth/signup_screen.dart similarity index 99% rename from lib/page/signup_screen.dart rename to lib/page/auth/signup_screen.dart index 851488ae..2f6f1f34 100644 --- a/lib/page/signup_screen.dart +++ b/lib/page/auth/signup_screen.dart @@ -10,7 +10,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/free_statistics.dart b/lib/page/balance/free_statistics.dart similarity index 99% rename from lib/page/free_statistics.dart rename to lib/page/balance/free_statistics.dart index 0d87f67e..c398d655 100644 --- a/lib/page/free_statistics.dart +++ b/lib/page/balance/free_statistics.dart @@ -4,7 +4,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/payment.dart b/lib/page/balance/payment.dart similarity index 99% rename from lib/page/payment.dart rename to lib/page/balance/payment.dart index 6fc0234f..227a0c8d 100644 --- a/lib/page/payment.dart +++ b/lib/page/balance/payment.dart @@ -11,7 +11,7 @@ import 'package:askaide/page/component/coin.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/payment.dart'; diff --git a/lib/page/quota_detail_screen.dart b/lib/page/balance/quota_detail_screen.dart similarity index 100% rename from lib/page/quota_detail_screen.dart rename to lib/page/balance/quota_detail_screen.dart diff --git a/lib/page/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart similarity index 100% rename from lib/page/quota_usage_statistics.dart rename to lib/page/balance/quota_usage_statistics.dart diff --git a/lib/page/rooms.dart b/lib/page/chat/component/room_item.dart similarity index 99% rename from lib/page/rooms.dart rename to lib/page/chat/component/room_item.dart index 50778e30..20b65b21 100644 --- a/lib/page/rooms.dart +++ b/lib/page/chat/component/room_item.dart @@ -7,7 +7,7 @@ import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/chat_chat.dart b/lib/page/chat/home.dart similarity index 99% rename from lib/page/chat_chat.dart rename to lib/page/chat/home.dart index 92ff1f7f..ccc9962e 100644 --- a/lib/page/chat_chat.dart +++ b/lib/page/chat/home.dart @@ -16,7 +16,7 @@ import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/model_indicator.dart'; import 'package:askaide/page/component/notify_message.dart'; import 'package:askaide/page/component/sliver_component.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; @@ -34,11 +34,11 @@ import 'package:go_router/go_router.dart'; import 'package:quickalert/models/quickalert_type.dart'; import 'package:url_launcher/url_launcher_string.dart'; -class ChatChatScreen extends StatefulWidget { +class HomePage extends StatefulWidget { final SettingRepository setting; final bool showInitialDialog; final int? reward; - const ChatChatScreen({ + const HomePage({ super.key, required this.setting, this.showInitialDialog = false, @@ -46,7 +46,7 @@ class ChatChatScreen extends StatefulWidget { }); @override - State createState() => _ChatChatScreenState(); + State createState() => _HomePageState(); } class ChatModel { @@ -63,7 +63,7 @@ class ChatModel { }); } -class _ChatChatScreenState extends State { +class _HomePageState extends State { final TextEditingController _textController = TextEditingController(); ModelIndicatorInfo? currentModel; diff --git a/lib/page/chat_anywhere.dart b/lib/page/chat/home_chat.dart similarity index 97% rename from lib/page/chat_anywhere.dart rename to lib/page/chat/home_chat.dart index 76c3f751..46f58148 100644 --- a/lib/page/chat_anywhere.dart +++ b/lib/page/chat/home_chat.dart @@ -5,7 +5,7 @@ import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/chat_screen.dart'; +import 'package:askaide/page/chat/room_chat.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/chat/chat_input.dart'; @@ -15,7 +15,7 @@ import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_error.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/model/message.dart'; @@ -27,7 +27,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:askaide/repo/model/model.dart' as mm; -class ChatAnywhereScreen extends StatefulWidget { +class HomeChatPage extends StatefulWidget { final MessageStateManager stateManager; final SettingRepository setting; final int? chatId; @@ -35,7 +35,7 @@ class ChatAnywhereScreen extends StatefulWidget { final String? model; final String? title; - const ChatAnywhereScreen({ + const HomeChatPage({ super.key, required this.stateManager, required this.setting, @@ -46,10 +46,10 @@ class ChatAnywhereScreen extends StatefulWidget { }); @override - State createState() => _ChatAnywhereScreenState(); + State createState() => _HomeChatPageState(); } -class _ChatAnywhereScreenState extends State { +class _HomeChatPageState extends State { final ChatPreviewController _chatPreviewController = ChatPreviewController(); final ScrollController _scrollController = ScrollController(); final ValueNotifier _inputEnabled = ValueNotifier(true); diff --git a/lib/page/chat_history.dart b/lib/page/chat/home_chat_history.dart similarity index 94% rename from lib/page/chat_history.dart rename to lib/page/chat/home_chat_history.dart index 6dfa504f..fa7a4fa3 100644 --- a/lib/page/chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -1,6 +1,6 @@ import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/chat_chat.dart'; +import 'package:askaide/page/chat/home.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/data/chat_history_datasource.dart'; @@ -15,18 +15,18 @@ import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; import 'package:loading_more_list/loading_more_list.dart'; -class ChatHistoryPage extends StatefulWidget { +class HomeChatHistoryPage extends StatefulWidget { final SettingRepository setting; final ChatMessageRepository chatMessageRepo; - const ChatHistoryPage( + const HomeChatHistoryPage( {super.key, required this.setting, required this.chatMessageRepo}); @override - State createState() => _ChatHistoryPageState(); + State createState() => _HomeChatHistoryPageState(); } -class _ChatHistoryPageState extends State { +class _HomeChatHistoryPageState extends State { late final ChatHistoryDatasource datasource; @override diff --git a/lib/page/chat_screen.dart b/lib/page/chat/room_chat.dart similarity index 99% rename from lib/page/chat_screen.dart rename to lib/page/chat/room_chat.dart index 47b19867..9f58b6a4 100644 --- a/lib/page/chat_screen.dart +++ b/lib/page/chat/room_chat.dart @@ -14,7 +14,6 @@ import 'package:askaide/page/component/effect/glass.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; @@ -31,14 +30,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; -import 'dialog.dart'; +import '../component/dialog.dart'; -class ChatScreen extends StatefulWidget { +class RoomChatPage extends StatefulWidget { final int roomId; final MessageStateManager stateManager; final SettingRepository setting; - const ChatScreen({ + const RoomChatPage({ super.key, required this.roomId, required this.stateManager, @@ -46,10 +45,10 @@ class ChatScreen extends StatefulWidget { }); @override - State createState() => _ChatScreenState(); + State createState() => _RoomChatPageState(); } -class _ChatScreenState extends State { +class _RoomChatPageState extends State { final ScrollController _scrollController = ScrollController(); final ValueNotifier _inputEnabled = ValueNotifier(true); final ChatPreviewController _chatPreviewController = ChatPreviewController(); diff --git a/lib/page/chat_room_create.dart b/lib/page/chat/room_create.dart similarity index 98% rename from lib/page/chat_room_create.dart rename to lib/page/chat/room_create.dart index b67d432f..66a011ea 100644 --- a/lib/page/chat_room_create.dart +++ b/lib/page/chat/room_create.dart @@ -29,21 +29,21 @@ import 'package:flutter_localization/flutter_localization.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/page/component/model_item.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart' as mm; import 'package:go_router/go_router.dart'; /// 创建聊天室对话框 -class ChatRoomCreateScreen extends StatefulWidget { +class RoomCreatePage extends StatefulWidget { final SettingRepository setting; - const ChatRoomCreateScreen({super.key, required this.setting}); + const RoomCreatePage({super.key, required this.setting}); @override - State createState() => _ChatRoomCreateScreenState(); + State createState() => _RoomCreatePageState(); } -class _ChatRoomCreateScreenState extends State { +class _RoomCreatePageState extends State { final _nameController = TextEditingController(text: ''); final _promptController = TextEditingController(text: ''); final _initMessageController = TextEditingController(text: ''); diff --git a/lib/page/chat_room_setting.dart b/lib/page/chat/room_edit.dart similarity index 98% rename from lib/page/chat_room_setting.dart rename to lib/page/chat/room_edit.dart index 43a174fb..a204312d 100644 --- a/lib/page/chat_room_setting.dart +++ b/lib/page/chat/room_edit.dart @@ -5,7 +5,7 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/chat_room_create.dart'; +import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/avatar_selector.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; @@ -19,7 +19,7 @@ import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/bloc/room_bloc.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/model.dart' as mm; import 'package:askaide/repo/settings_repo.dart'; @@ -29,17 +29,16 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; -class ChatRoomSettingScreen extends StatefulWidget { +class RoomEditPage extends StatefulWidget { final int roomId; final SettingRepository setting; - const ChatRoomSettingScreen( - {super.key, required this.roomId, required this.setting}); + const RoomEditPage({super.key, required this.roomId, required this.setting}); @override - State createState() => _ChatRoomSettingScreenState(); + State createState() => _RoomEditPageState(); } -class _ChatRoomSettingScreenState extends State { +class _RoomEditPageState extends State { final _nameController = TextEditingController(); final _promptController = TextEditingController(text: ''); final _initMessageController = TextEditingController(text: ''); diff --git a/lib/page/home_screen.dart b/lib/page/chat/rooms.dart similarity index 97% rename from lib/page/home_screen.dart rename to lib/page/chat/rooms.dart index 9837a8e1..e39de106 100644 --- a/lib/page/home_screen.dart +++ b/lib/page/chat/rooms.dart @@ -10,8 +10,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/room_card.dart'; import 'package:askaide/page/component/sliver_component.dart'; import 'package:askaide/page/component/weak_text_button.dart'; -import 'package:askaide/page/dialog.dart'; -import 'package:askaide/page/rooms.dart'; +import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/page/chat/component/room_item.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/room_gallery.dart'; @@ -21,15 +21,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; -class CharactersScreen extends StatefulWidget { +class RoomsPage extends StatefulWidget { final SettingRepository setting; - const CharactersScreen({Key? key, required this.setting}) : super(key: key); + const RoomsPage({Key? key, required this.setting}) : super(key: key); @override - State createState() => _CharactersScreenState(); + State createState() => _RoomsPageState(); } -class _CharactersScreenState extends State { +class _RoomsPageState extends State { @override void initState() { context.read().add(RoomsLoadEvent()); diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index e950b148..46803cb6 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -4,7 +4,7 @@ import 'package:askaide/helper/platform.dart'; import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/chat/voice_record.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index d662f840..38391661 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -10,7 +10,7 @@ import 'package:askaide/page/component/attached_button_panel.dart'; import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/share.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index 381928c4..ddf29671 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -10,7 +10,7 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/share.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/component/chat/voice_record.dart b/lib/page/component/chat/voice_record.dart index 30e5bb5a..9560e7ff 100644 --- a/lib/page/component/chat/voice_record.dart +++ b/lib/page/component/chat/voice_record.dart @@ -5,7 +5,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/model_resolver.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/dialog.dart b/lib/page/component/dialog.dart similarity index 100% rename from lib/page/dialog.dart rename to lib/page/component/dialog.dart diff --git a/lib/page/component/gallery_item_share.dart b/lib/page/component/gallery_item_share.dart index 008f7eb7..c5abd771 100644 --- a/lib/page/component/gallery_item_share.dart +++ b/lib/page/component/gallery_item_share.dart @@ -9,7 +9,7 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/share.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/gallery/gallery_item.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; diff --git a/lib/page/component/image_preview.dart b/lib/page/component/image_preview.dart index 5c9ef10b..ef47de2c 100644 --- a/lib/page/component/image_preview.dart +++ b/lib/page/component/image_preview.dart @@ -7,7 +7,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/gallery_item_share.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:before_after/before_after.dart'; diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index f24ec5cf..c5122119 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/room_card.dart b/lib/page/component/room_card.dart index f25f70b8..8de39932 100644 --- a/lib/page/component/room_card.dart +++ b/lib/page/component/room_card.dart @@ -3,7 +3,7 @@ import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/weak_text_button.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/share.dart b/lib/page/component/share.dart index cfcb8c2d..7638dd90 100644 --- a/lib/page/component/share.dart +++ b/lib/page/component/share.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:askaide/helper/platform.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:flutter/material.dart'; import 'package:fluwx/fluwx.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/page/component/verify_code_input.dart b/lib/page/component/verify_code_input.dart index c952d229..e4bd735c 100644 --- a/lib/page/component/verify_code_input.dart +++ b/lib/page/component/verify_code_input.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/page/creative_island/creative_island_create_page.dart b/lib/page/creative_island/creative_island_create_page.dart index 84551588..83006a48 100644 --- a/lib/page/creative_island/creative_island_create_page.dart +++ b/lib/page/creative_island/creative_island_create_page.dart @@ -16,7 +16,7 @@ import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; diff --git a/lib/page/creative_island/creative_island_gallery.dart b/lib/page/creative_island/creative_island_gallery.dart index 747a8445..554ebf47 100644 --- a/lib/page/creative_island/creative_island_gallery.dart +++ b/lib/page/creative_island/creative_island_gallery.dart @@ -2,7 +2,7 @@ import 'package:askaide/bloc/creative_island_bloc.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/image_preview.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/creative_island/creative_island_history.dart b/lib/page/creative_island/creative_island_history.dart index db895461..32a4cb30 100644 --- a/lib/page/creative_island/creative_island_history.dart +++ b/lib/page/creative_island/creative_island_history.dart @@ -5,7 +5,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/creative_island/creative_island.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; diff --git a/lib/page/creative_island/creative_island_history_all.dart b/lib/page/creative_island/creative_island_history_all.dart index 43b92e9e..e3185def 100644 --- a/lib/page/creative_island/creative_island_history_all.dart +++ b/lib/page/creative_island/creative_island_history_all.dart @@ -7,7 +7,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/draw/data/draw_history_datasource.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; diff --git a/lib/page/creative_island/creative_island_history_preview.dart b/lib/page/creative_island/creative_island_history_preview.dart index f2bbc9ee..7ccf39a3 100644 --- a/lib/page/creative_island/creative_island_history_preview.dart +++ b/lib/page/creative_island/creative_island_history_preview.dart @@ -3,7 +3,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; diff --git a/lib/page/creative_island/creative_island_result.dart b/lib/page/creative_island/creative_island_result.dart index 119cca18..25a1b9f7 100644 --- a/lib/page/creative_island/creative_island_result.dart +++ b/lib/page/creative_island/creative_island_result.dart @@ -2,7 +2,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/draw/components/image_style_selector.dart b/lib/page/draw/components/image_style_selector.dart index 4b2f0b30..6059b1b6 100644 --- a/lib/page/draw/components/image_style_selector.dart +++ b/lib/page/draw/components/image_style_selector.dart @@ -1,7 +1,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_input.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/draw/draw_create.dart b/lib/page/draw/draw_create.dart index a459227c..72fb3ec9 100644 --- a/lib/page/draw/draw_create.dart +++ b/lib/page/draw/draw_create.dart @@ -14,7 +14,7 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/prompt_tags_selector.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/draw/components/image_selector.dart'; import 'package:askaide/page/draw/components/image_size.dart'; import 'package:askaide/page/draw/components/image_style_selector.dart'; diff --git a/lib/page/draw/image_edit_direct.dart b/lib/page/draw/image_edit_direct.dart index c1a38873..686bac7f 100644 --- a/lib/page/draw/image_edit_direct.dart +++ b/lib/page/draw/image_edit_direct.dart @@ -8,7 +8,7 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/draw/components/image_selector.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; diff --git a/lib/page/gallery/gallery_item.dart b/lib/page/gallery/gallery_item.dart index fd89f614..02e14009 100644 --- a/lib/page/gallery/gallery_item.dart +++ b/lib/page/gallery/gallery_item.dart @@ -11,7 +11,7 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/gallery_item_share.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/group/chat.dart b/lib/page/group/chat.dart index 0c3df229..bf95600a 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/group/chat.dart @@ -14,7 +14,7 @@ import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/page/component/chat/chat_input.dart'; diff --git a/lib/page/group/create.dart b/lib/page/group/create.dart index 3ae3d4e0..511693cf 100644 --- a/lib/page/group/create.dart +++ b/lib/page/group/create.dart @@ -17,7 +17,7 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/group/edit.dart b/lib/page/group/edit.dart index 8e7a9b2a..0114b1e8 100644 --- a/lib/page/group/edit.dart +++ b/lib/page/group/edit.dart @@ -21,7 +21,7 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/avatar_selector.dart b/lib/page/lab/avatar_selector.dart similarity index 100% rename from lib/page/avatar_selector.dart rename to lib/page/lab/avatar_selector.dart diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index 1cd6aa65..15fc2433 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -9,7 +9,7 @@ import 'package:askaide/page/component/enhanced_input.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/item_selector_search.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api/image_model.dart'; diff --git a/lib/page/lab/draw_board.dart b/lib/page/lab/draw_board.dart index 55043471..739b3e42 100644 --- a/lib/page/lab/draw_board.dart +++ b/lib/page/lab/draw_board.dart @@ -5,7 +5,7 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/column_block.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/draw/components/image_selector.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:file_saver/file_saver.dart'; diff --git a/lib/page/prompt.dart b/lib/page/lab/prompt.dart similarity index 98% rename from lib/page/prompt.dart rename to lib/page/lab/prompt.dart index 01e6aeb8..2144e0ab 100644 --- a/lib/page/prompt.dart +++ b/lib/page/lab/prompt.dart @@ -4,7 +4,7 @@ import 'package:askaide/page/component/effect/glass.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/item_selector_search.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/lab/user_center.dart b/lib/page/lab/user_center.dart index 19658162..370955de 100644 --- a/lib/page/lab/user_center.dart +++ b/lib/page/lab/user_center.dart @@ -4,7 +4,7 @@ import 'package:askaide/page/component/account_quota_card.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/invite_card.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/account_security.dart b/lib/page/setting/account_security.dart similarity index 99% rename from lib/page/account_security.dart rename to lib/page/setting/account_security.dart index b0dc83d9..f78228eb 100644 --- a/lib/page/account_security.dart +++ b/lib/page/setting/account_security.dart @@ -3,7 +3,7 @@ import 'package:askaide/helper/logger.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/background_selector.dart b/lib/page/setting/background_selector.dart similarity index 99% rename from lib/page/background_selector.dart rename to lib/page/setting/background_selector.dart index af440631..f8525ea5 100644 --- a/lib/page/background_selector.dart +++ b/lib/page/setting/background_selector.dart @@ -7,7 +7,7 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:file_picker/file_picker.dart'; diff --git a/lib/page/bind_phone_page.dart b/lib/page/setting/bind_phone_page.dart similarity index 99% rename from lib/page/bind_phone_page.dart rename to lib/page/setting/bind_phone_page.dart index 94fcb265..192c545f 100644 --- a/lib/page/bind_phone_page.dart +++ b/lib/page/setting/bind_phone_page.dart @@ -8,7 +8,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/verify_code_input.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/change_password.dart b/lib/page/setting/change_password.dart similarity index 98% rename from lib/page/change_password.dart rename to lib/page/setting/change_password.dart index a69af574..47259889 100644 --- a/lib/page/change_password.dart +++ b/lib/page/setting/change_password.dart @@ -5,7 +5,7 @@ import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/custom_home_models.dart b/lib/page/setting/custom_home_models.dart similarity index 98% rename from lib/page/custom_home_models.dart rename to lib/page/setting/custom_home_models.dart index 953c35a2..e2776348 100644 --- a/lib/page/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -1,14 +1,14 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/color.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/chat_room_create.dart'; +import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/model_indicator.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/destroy_account.dart b/lib/page/setting/destroy_account.dart similarity index 98% rename from lib/page/destroy_account.dart rename to lib/page/setting/destroy_account.dart index f916ff4b..b7b69674 100644 --- a/lib/page/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -6,7 +6,7 @@ import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/verify_code_input.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/diagnosis.dart b/lib/page/setting/diagnosis.dart similarity index 98% rename from lib/page/diagnosis.dart rename to lib/page/setting/diagnosis.dart index 90c55ecf..810cb4d3 100644 --- a/lib/page/diagnosis.dart +++ b/lib/page/setting/diagnosis.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/openai_setting.dart b/lib/page/setting/openai_setting.dart similarity index 99% rename from lib/page/openai_setting.dart rename to lib/page/setting/openai_setting.dart index 743b40aa..2adade36 100644 --- a/lib/page/openai_setting.dart +++ b/lib/page/setting/openai_setting.dart @@ -7,7 +7,7 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/retrieve_password_screen.dart b/lib/page/setting/retrieve_password_screen.dart similarity index 99% rename from lib/page/retrieve_password_screen.dart rename to lib/page/setting/retrieve_password_screen.dart index b14fd5e6..ca340f50 100644 --- a/lib/page/retrieve_password_screen.dart +++ b/lib/page/setting/retrieve_password_screen.dart @@ -4,7 +4,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/setting_screen.dart b/lib/page/setting/setting_screen.dart similarity index 99% rename from lib/page/setting_screen.dart rename to lib/page/setting/setting_screen.dart index 1710e45d..70d6833a 100644 --- a/lib/page/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -9,7 +9,7 @@ import 'package:askaide/helper/http.dart'; import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/account_security.dart'; +import 'package:askaide/page/setting/account_security.dart'; import 'package:askaide/page/component/account_quota_card.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/invite_card.dart'; @@ -20,7 +20,7 @@ import 'package:askaide/page/theme/custom_size.dart'; import 'package:askaide/page/theme/custom_theme.dart'; import 'package:askaide/page/theme/theme.dart'; import 'package:askaide/helper/constant.dart'; -import 'package:askaide/page/dialog.dart'; +import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/cupertino.dart'; From cbbed8ccae081b675fc9944083a8d6f11d04d6d0 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 10:14:28 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84?= =?UTF-8?q?=E9=87=8D=E6=96=B0=E7=BB=84=E7=BB=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 20 ++++++++-------- lib/page/app_scaffold.dart | 2 +- lib/page/auth/signin_or_signup.dart | 4 ++-- lib/page/auth/signin_screen.dart | 4 ++-- lib/page/auth/signup_screen.dart | 2 +- lib/page/balance/free_statistics.dart | 4 ++-- lib/page/balance/payment.dart | 4 ++-- lib/page/balance/quota_detail_screen.dart | 4 ++-- lib/page/balance/quota_usage_statistics.dart | 4 ++-- lib/page/chat/component/room_item.dart | 2 +- lib/page/{ => chat}/group/chat.dart | 4 ++-- lib/page/{ => chat}/group/create.dart | 4 ++-- lib/page/{ => chat}/group/edit.dart | 4 ++-- lib/page/chat/home.dart | 4 ++-- lib/page/chat/home_chat.dart | 4 ++-- lib/page/chat/home_chat_history.dart | 4 ++-- lib/page/chat/room_chat.dart | 4 ++-- lib/page/chat/room_create.dart | 4 ++-- lib/page/chat/room_edit.dart | 4 ++-- lib/page/chat/rooms.dart | 23 +++++++++++++------ lib/page/component/account_quota_card.dart | 2 +- lib/page/component/audio_player.dart | 2 +- lib/page/component/avatar_selector.dart | 2 +- lib/page/component/background_container.dart | 4 ++-- lib/page/component/bottom_sheet_box.dart | 2 +- lib/page/component/chat/chat_input.dart | 4 ++-- lib/page/component/chat/chat_preview.dart | 4 ++-- lib/page/component/chat/chat_share.dart | 4 ++-- lib/page/component/chat/empty.dart | 2 +- lib/page/component/chat/help_tips.dart | 2 +- lib/page/component/chat/markdown.dart | 2 +- lib/page/component/chat/voice_record.dart | 2 +- lib/page/component/chat_tools_button.dart | 2 +- lib/page/component/column_block.dart | 2 +- lib/page/component/dialog.dart | 2 +- lib/page/component/enhanced_button.dart | 2 +- lib/page/component/enhanced_input.dart | 2 +- lib/page/component/enhanced_textfield.dart | 4 ++-- lib/page/component/gallery_item_share.dart | 6 ++--- lib/page/component/image_preview.dart | 4 ++-- lib/page/component/item_selector_search.dart | 2 +- lib/page/component/loading.dart | 2 +- lib/page/component/model_indicator.dart | 2 +- lib/page/component/model_item.dart | 2 +- lib/page/component/multi_item_selector.dart | 2 +- lib/page/component/password_field.dart | 2 +- lib/page/component/prompt_tags_selector.dart | 2 +- lib/page/component/room_card.dart | 2 +- lib/page/component/sliver_component.dart | 4 ++-- .../{ => component}/theme/custom_size.dart | 0 .../{ => component}/theme/custom_theme.dart | 0 lib/page/{ => component}/theme/theme.dart | 0 lib/page/component/verify_code_input.dart | 2 +- lib/page/component/weak_text_button.dart | 2 +- lib/page/creative_island/content_preview.dart | 2 +- lib/page/creative_island/creative_island.dart | 4 ++-- .../creative_island_create_page.dart | 4 ++-- .../creative_island_gallery.dart | 4 ++-- .../creative_island_history.dart | 4 ++-- .../creative_island_history_all.dart | 6 ++--- .../creative_island_history_preview.dart | 4 ++-- .../creative_island_result.dart | 4 ++-- .../draw/components/creative_item.dart | 2 +- .../draw/components/image_selector.dart | 2 +- .../draw/components/image_size.dart | 2 +- .../draw/components/image_style_selector.dart | 2 +- .../draw/data/draw_history_datasource.dart | 0 lib/page/{ => creative_island}/draw/draw.dart | 6 ++--- .../draw/draw_create.dart | 10 ++++---- .../draw/image_edit_direct.dart | 6 ++--- .../gallery/components/image_card.dart | 2 +- .../gallery/data/gallery_datasource.dart | 0 .../gallery/gallery.dart | 8 +++---- .../gallery/gallery_item.dart | 4 ++-- lib/page/lab/creative_models.dart | 4 ++-- lib/page/lab/draw_board.dart | 4 ++-- lib/page/lab/prompt.dart | 2 +- lib/page/lab/user_center.dart | 2 +- lib/page/setting/account_security.dart | 4 ++-- lib/page/setting/background_selector.dart | 2 +- lib/page/setting/bind_phone_page.dart | 4 ++-- lib/page/setting/change_password.dart | 4 ++-- lib/page/setting/custom_home_models.dart | 4 ++-- lib/page/setting/destroy_account.dart | 4 ++-- lib/page/setting/diagnosis.dart | 4 ++-- lib/page/setting/openai_setting.dart | 4 ++-- .../setting/retrieve_password_screen.dart | 4 ++-- lib/page/setting/setting_screen.dart | 6 ++--- 88 files changed, 163 insertions(+), 154 deletions(-) rename lib/page/{ => chat}/group/chat.dart (99%) rename lib/page/{ => chat}/group/create.dart (99%) rename lib/page/{ => chat}/group/edit.dart (99%) rename lib/page/{ => component}/theme/custom_size.dart (100%) rename lib/page/{ => component}/theme/custom_theme.dart (100%) rename lib/page/{ => component}/theme/theme.dart (100%) rename lib/page/{ => creative_island}/draw/components/creative_item.dart (96%) rename lib/page/{ => creative_island}/draw/components/image_selector.dart (99%) rename lib/page/{ => creative_island}/draw/components/image_size.dart (94%) rename lib/page/{ => creative_island}/draw/components/image_style_selector.dart (98%) rename lib/page/{ => creative_island}/draw/data/draw_history_datasource.dart (100%) rename lib/page/{ => creative_island}/draw/draw.dart (95%) rename lib/page/{ => creative_island}/draw/draw_create.dart (98%) rename lib/page/{ => creative_island}/draw/image_edit_direct.dart (97%) rename lib/page/{ => creative_island}/gallery/components/image_card.dart (98%) rename lib/page/{ => creative_island}/gallery/data/gallery_datasource.dart (100%) rename lib/page/{ => creative_island}/gallery/gallery.dart (94%) rename lib/page/{ => creative_island}/gallery/gallery_item.dart (99%) diff --git a/lib/main.dart b/lib/main.dart index a1cd2e8b..fe7079ca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,18 +35,18 @@ import 'package:askaide/page/creative_island/creative_island_history_all.dart'; import 'package:askaide/page/creative_island/creative_island_history_preview.dart'; import 'package:askaide/page/setting/custom_home_models.dart'; import 'package:askaide/page/balance/free_statistics.dart'; -import 'package:askaide/page/group/chat.dart'; -import 'package:askaide/page/group/create.dart'; -import 'package:askaide/page/group/edit.dart'; +import 'package:askaide/page/chat/group/chat.dart'; +import 'package:askaide/page/chat/group/create.dart'; +import 'package:askaide/page/chat/group/edit.dart'; import 'package:askaide/page/lab/creative_models.dart'; import 'package:askaide/page/setting/destroy_account.dart'; import 'package:askaide/page/setting/diagnosis.dart'; -import 'package:askaide/page/draw/draw.dart'; -import 'package:askaide/page/draw/draw_create.dart'; -import 'package:askaide/page/draw/image_edit_direct.dart'; +import 'package:askaide/page/creative_island/draw/draw.dart'; +import 'package:askaide/page/creative_island/draw/draw_create.dart'; +import 'package:askaide/page/creative_island/draw/image_edit_direct.dart'; import 'package:askaide/page/lab/draw_board.dart'; -import 'package:askaide/page/gallery/gallery.dart'; -import 'package:askaide/page/gallery/gallery_item.dart'; +import 'package:askaide/page/creative_island/gallery/gallery.dart'; +import 'package:askaide/page/creative_island/gallery/gallery_item.dart'; import 'package:askaide/page/setting/openai_setting.dart'; import 'package:askaide/page/balance/payment.dart'; import 'package:askaide/page/lab/prompt.dart'; @@ -69,7 +69,7 @@ import 'package:askaide/repo/deepai_repo.dart'; import 'package:askaide/repo/stabilityai_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:askaide/helper/constant.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:fluwx/fluwx.dart'; @@ -92,7 +92,7 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; -import 'page/theme/theme.dart'; +import 'page/component/theme/theme.dart'; import 'package:sizer/sizer.dart'; import 'package:askaide/helper/http.dart' as httpx; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; diff --git a/lib/page/app_scaffold.dart b/lib/page/app_scaffold.dart index 457a869e..02572cf4 100644 --- a/lib/page/app_scaffold.dart +++ b/lib/page/app_scaffold.dart @@ -3,7 +3,7 @@ import 'package:askaide/helper/event.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/auth/signin_or_signup.dart b/lib/page/auth/signin_or_signup.dart index c8811a9e..3d98a1b2 100644 --- a/lib/page/auth/signin_or_signup.dart +++ b/lib/page/auth/signin_or_signup.dart @@ -9,8 +9,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/auth/signin_screen.dart b/lib/page/auth/signin_screen.dart index 16a81d48..d4c2a205 100644 --- a/lib/page/auth/signin_screen.dart +++ b/lib/page/auth/signin_screen.dart @@ -11,8 +11,8 @@ import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/gestures.dart'; diff --git a/lib/page/auth/signup_screen.dart b/lib/page/auth/signup_screen.dart index 2f6f1f34..0067d234 100644 --- a/lib/page/auth/signup_screen.dart +++ b/lib/page/auth/signup_screen.dart @@ -11,7 +11,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/balance/free_statistics.dart b/lib/page/balance/free_statistics.dart index c398d655..3f8e3c87 100644 --- a/lib/page/balance/free_statistics.dart +++ b/lib/page/balance/free_statistics.dart @@ -5,8 +5,8 @@ import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/balance/payment.dart b/lib/page/balance/payment.dart index 227a0c8d..9986a8e6 100644 --- a/lib/page/balance/payment.dart +++ b/lib/page/balance/payment.dart @@ -12,8 +12,8 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/payment.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/balance/quota_detail_screen.dart b/lib/page/balance/quota_detail_screen.dart index 55f5219a..296e780a 100644 --- a/lib/page/balance/quota_detail_screen.dart +++ b/lib/page/balance/quota_detail_screen.dart @@ -2,8 +2,8 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/coin.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/quota.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 1accb901..1355eb80 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -1,8 +1,8 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/message_box.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index 20b65b21..f5cb446b 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -8,7 +8,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/group/chat.dart b/lib/page/chat/group/chat.dart similarity index 99% rename from lib/page/group/chat.dart rename to lib/page/chat/group/chat.dart index bf95600a..e0529297 100644 --- a/lib/page/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -15,8 +15,8 @@ import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/page/component/chat/chat_input.dart'; import 'package:askaide/page/component/chat/chat_preview.dart'; import 'package:askaide/repo/model/group.dart'; diff --git a/lib/page/group/create.dart b/lib/page/chat/group/create.dart similarity index 99% rename from lib/page/group/create.dart rename to lib/page/chat/group/create.dart index 511693cf..520a74b6 100644 --- a/lib/page/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -18,8 +18,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/group.dart'; import 'package:askaide/repo/model/misc.dart'; diff --git a/lib/page/group/edit.dart b/lib/page/chat/group/edit.dart similarity index 99% rename from lib/page/group/edit.dart rename to lib/page/chat/group/edit.dart index 0114b1e8..164bc9d5 100644 --- a/lib/page/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -22,8 +22,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index ccc9962e..ea70adde 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -17,8 +17,8 @@ import 'package:askaide/page/component/model_indicator.dart'; import 'package:askaide/page/component/notify_message.dart'; import 'package:askaide/page/component/sliver_component.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/chat_history.dart'; import 'package:askaide/repo/model/misc.dart'; diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 46f58148..4e84009a 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -16,8 +16,8 @@ import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_error.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; diff --git a/lib/page/chat/home_chat_history.dart b/lib/page/chat/home_chat_history.dart index fa7a4fa3..e2a3422a 100644 --- a/lib/page/chat/home_chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -4,8 +4,8 @@ import 'package:askaide/page/chat/home.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/data/chat_history_datasource.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/chat_message_repo.dart'; import 'package:askaide/repo/model/chat_history.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 9f58b6a4..70fd2b2c 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -14,8 +14,8 @@ import 'package:askaide/page/component/effect/glass.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/bloc/notify_bloc.dart'; diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 66a011ea..59f7d161 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -18,7 +18,7 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/room_card.dart'; import 'package:askaide/page/component/weak_text_button.dart'; -import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; @@ -30,7 +30,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/model.dart'; import 'package:askaide/page/component/model_item.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart' as mm; import 'package:go_router/go_router.dart'; diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index a204312d..569e5e8d 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -16,8 +16,8 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index e39de106..36ff954a 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -12,8 +12,8 @@ import 'package:askaide/page/component/sliver_component.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/chat/component/room_item.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; @@ -70,6 +70,7 @@ class _RoomsPageState extends State { return SliverComponent( actions: [ + // 数字人创建按钮 if (selectedSuggestions.isEmpty) EnhancedPopupMenu( items: [ @@ -88,11 +89,19 @@ class _RoomsPageState extends State { title: '发起群聊', icon: Icons.chat_bubble_outline, onTap: (p0) { - context - .push('/group-chat-create') - .whenComplete(() { - context.read().add(RoomsLoadEvent()); - }); + // context + // .push('/group-chat-create') + // .whenComplete(() { + // context.read().add(RoomsLoadEvent()); + // }); + + openModalBottomSheet( + context, + (context) { + return Container(); + }, + heightFactor: 0.8, + ); }, ) ], diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 121f9bf7..bf9227c0 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -3,7 +3,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/coin.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/component/audio_player.dart b/lib/page/component/audio_player.dart index 434987a1..ad2bdd72 100644 --- a/lib/page/component/audio_player.dart +++ b/lib/page/component/audio_player.dart @@ -1,6 +1,6 @@ import 'package:askaide/helper/logger.dart'; import 'package:askaide/helper/platform.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/avatar_selector.dart b/lib/page/component/avatar_selector.dart index f2bc6771..db3a77d7 100644 --- a/lib/page/component/avatar_selector.dart +++ b/lib/page/component/avatar_selector.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/background_container.dart b/lib/page/component/background_container.dart index 5e3ac678..9c9a0b74 100644 --- a/lib/page/component/background_container.dart +++ b/lib/page/component/background_container.dart @@ -2,8 +2,8 @@ import 'dart:ui'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/bottom_sheet_box.dart b/lib/page/component/bottom_sheet_box.dart index 0a466345..ddc4c446 100644 --- a/lib/page/component/bottom_sheet_box.dart +++ b/lib/page/component/bottom_sheet_box.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class BottomSheetBox extends StatelessWidget { diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index 46803cb6..8b2d1214 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -5,7 +5,7 @@ import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/chat/voice_record.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:file_picker/file_picker.dart'; @@ -15,7 +15,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; class ChatInput extends StatefulWidget { final Function(String value) onSubmit; diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 38391661..98ff2421 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -11,10 +11,10 @@ import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:bot_toast/bot_toast.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/page/component/chat/markdown.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:clipboard/clipboard.dart'; diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index ddf29671..acf2c2cc 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -11,8 +11,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/component/chat/empty.dart b/lib/page/component/chat/empty.dart index fb366ead..1628fa1b 100644 --- a/lib/page/component/chat/empty.dart +++ b/lib/page/component/chat/empty.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/chat/help_tips.dart b/lib/page/component/chat/help_tips.dart index 1bbbbdcb..f7f51728 100644 --- a/lib/page/component/chat/help_tips.dart +++ b/lib/page/component/chat/help_tips.dart @@ -1,7 +1,7 @@ import 'dart:math'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/component/chat/markdown.dart b/lib/page/component/chat/markdown.dart index 722667d1..859e3848 100644 --- a/lib/page/component/chat/markdown.dart +++ b/lib/page/component/chat/markdown.dart @@ -1,5 +1,5 @@ import 'package:askaide/page/component/image_preview.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as md; diff --git a/lib/page/component/chat/voice_record.dart b/lib/page/component/chat/voice_record.dart index 9560e7ff..12c38807 100644 --- a/lib/page/component/chat/voice_record.dart +++ b/lib/page/component/chat/voice_record.dart @@ -6,7 +6,7 @@ import 'package:askaide/helper/model_resolver.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/component/chat_tools_button.dart b/lib/page/component/chat_tools_button.dart index 85a39c38..1dd352a5 100644 --- a/lib/page/component/chat_tools_button.dart +++ b/lib/page/component/chat_tools_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; class ChatToolsButton extends StatefulWidget { final String text; diff --git a/lib/page/component/column_block.dart b/lib/page/component/column_block.dart index 267361f2..ca43f717 100644 --- a/lib/page/component/column_block.dart +++ b/lib/page/component/column_block.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class ColumnBlock extends StatelessWidget { diff --git a/lib/page/component/dialog.dart b/lib/page/component/dialog.dart index 06c3d2f0..703b126d 100644 --- a/lib/page/component/dialog.dart +++ b/lib/page/component/dialog.dart @@ -8,7 +8,7 @@ import 'package:askaide/page/component/bottom_sheet_box.dart'; import 'package:askaide/page/component/button.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/item_selector_search.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/component/enhanced_button.dart b/lib/page/component/enhanced_button.dart index d6ffd5a5..7966a9d6 100644 --- a/lib/page/component/enhanced_button.dart +++ b/lib/page/component/enhanced_button.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class EnhancedButton extends StatelessWidget { diff --git a/lib/page/component/enhanced_input.dart b/lib/page/component/enhanced_input.dart index cf9dff01..79ea5560 100644 --- a/lib/page/component/enhanced_input.dart +++ b/lib/page/component/enhanced_input.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/enhanced_textfield.dart b/lib/page/component/enhanced_textfield.dart index 5ca1db0f..3759777c 100644 --- a/lib/page/component/enhanced_textfield.dart +++ b/lib/page/component/enhanced_textfield.dart @@ -1,5 +1,5 @@ -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/page/component/gallery_item_share.dart b/lib/page/component/gallery_item_share.dart index c5abd771..959c8647 100644 --- a/lib/page/component/gallery_item_share.dart +++ b/lib/page/component/gallery_item_share.dart @@ -10,9 +10,9 @@ import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/gallery/gallery_item.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/gallery/gallery_item.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:file_saver/file_saver.dart'; diff --git a/lib/page/component/image_preview.dart b/lib/page/component/image_preview.dart index ef47de2c..6f60b77a 100644 --- a/lib/page/component/image_preview.dart +++ b/lib/page/component/image_preview.dart @@ -8,8 +8,8 @@ import 'package:askaide/page/component/gallery_item_share.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:before_after/before_after.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:file_saver/file_saver.dart'; diff --git a/lib/page/component/item_selector_search.dart b/lib/page/component/item_selector_search.dart index 1d1dccc7..002d81fb 100644 --- a/lib/page/component/item_selector_search.dart +++ b/lib/page/component/item_selector_search.dart @@ -1,5 +1,5 @@ import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/page/component/loading.dart b/lib/page/component/loading.dart index 0f2c4833..518453e4 100644 --- a/lib/page/component/loading.dart +++ b/lib/page/component/loading.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:loading_animation_widget/loading_animation_widget.dart'; diff --git a/lib/page/component/model_indicator.dart b/lib/page/component/model_indicator.dart index 28d50023..b6f6465c 100644 --- a/lib/page/component/model_indicator.dart +++ b/lib/page/component/model_indicator.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class ModelIndicatorInfo { diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index c5122119..440126c9 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -1,5 +1,5 @@ import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/multi_item_selector.dart b/lib/page/component/multi_item_selector.dart index 3eaec44b..16a33ee9 100644 --- a/lib/page/component/multi_item_selector.dart +++ b/lib/page/component/multi_item_selector.dart @@ -1,6 +1,6 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_button.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/page/component/password_field.dart b/lib/page/component/password_field.dart index 79b6caa2..22314679 100644 --- a/lib/page/component/password_field.dart +++ b/lib/page/component/password_field.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; diff --git a/lib/page/component/prompt_tags_selector.dart b/lib/page/component/prompt_tags_selector.dart index 3ea17522..8ef75f60 100644 --- a/lib/page/component/prompt_tags_selector.dart +++ b/lib/page/component/prompt_tags_selector.dart @@ -1,6 +1,6 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/weak_text_button.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/component/room_card.dart b/lib/page/component/room_card.dart index 8de39932..f39c0159 100644 --- a/lib/page/component/room_card.dart +++ b/lib/page/component/room_card.dart @@ -4,7 +4,7 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/room_gallery.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/page/component/sliver_component.dart b/lib/page/component/sliver_component.dart index 8d8d8a51..31d59977 100644 --- a/lib/page/component/sliver_component.dart +++ b/lib/page/component/sliver_component.dart @@ -1,6 +1,6 @@ import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class SliverSingleComponent extends StatelessWidget { diff --git a/lib/page/theme/custom_size.dart b/lib/page/component/theme/custom_size.dart similarity index 100% rename from lib/page/theme/custom_size.dart rename to lib/page/component/theme/custom_size.dart diff --git a/lib/page/theme/custom_theme.dart b/lib/page/component/theme/custom_theme.dart similarity index 100% rename from lib/page/theme/custom_theme.dart rename to lib/page/component/theme/custom_theme.dart diff --git a/lib/page/theme/theme.dart b/lib/page/component/theme/theme.dart similarity index 100% rename from lib/page/theme/theme.dart rename to lib/page/component/theme/theme.dart diff --git a/lib/page/component/verify_code_input.dart b/lib/page/component/verify_code_input.dart index e4bd735c..ab96b4d7 100644 --- a/lib/page/component/verify_code_input.dart +++ b/lib/page/component/verify_code_input.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/component/weak_text_button.dart b/lib/page/component/weak_text_button.dart index 9fc52300..bf241fa7 100644 --- a/lib/page/component/weak_text_button.dart +++ b/lib/page/component/weak_text_button.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class WeakTextButton extends StatelessWidget { diff --git a/lib/page/creative_island/content_preview.dart b/lib/page/creative_island/content_preview.dart index 1aedf0ce..e70e9178 100644 --- a/lib/page/creative_island/content_preview.dart +++ b/lib/page/creative_island/content_preview.dart @@ -1,7 +1,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/image_preview.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; diff --git a/lib/page/creative_island/creative_island.dart b/lib/page/creative_island/creative_island.dart index 9daab211..8cca45cb 100644 --- a/lib/page/creative_island/creative_island.dart +++ b/lib/page/creative_island/creative_island.dart @@ -8,8 +8,8 @@ import 'package:askaide/page/component/enhanced_error.dart'; import 'package:askaide/page/component/sliver_component.dart'; import 'package:askaide/page/component/weak_text_button.dart'; import 'package:askaide/page/creative_island/box.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/creative_island/creative_island_create_page.dart b/lib/page/creative_island/creative_island_create_page.dart index 83006a48..12e079b5 100644 --- a/lib/page/creative_island/creative_island_create_page.dart +++ b/lib/page/creative_island/creative_island_create_page.dart @@ -17,8 +17,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/creative_island_repo.dart'; diff --git a/lib/page/creative_island/creative_island_gallery.dart b/lib/page/creative_island/creative_island_gallery.dart index 554ebf47..068b2137 100644 --- a/lib/page/creative_island/creative_island_gallery.dart +++ b/lib/page/creative_island/creative_island_gallery.dart @@ -3,8 +3,8 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/creative_island/creative_island_history.dart b/lib/page/creative_island/creative_island_history.dart index 32a4cb30..209c1a34 100644 --- a/lib/page/creative_island/creative_island_history.dart +++ b/lib/page/creative_island/creative_island_history.dart @@ -6,8 +6,8 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/creative_island/creative_island.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/creative_island_repo.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/creative_island/creative_island_history_all.dart b/lib/page/creative_island/creative_island_history_all.dart index e3185def..fae813c5 100644 --- a/lib/page/creative_island/creative_island_history_all.dart +++ b/lib/page/creative_island/creative_island_history_all.dart @@ -8,9 +8,9 @@ import 'package:askaide/page/component/button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/draw/data/draw_history_datasource.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/draw/data/draw_history_datasource.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/creative_island/creative_island_history_preview.dart b/lib/page/creative_island/creative_island_history_preview.dart index 7ccf39a3..df3f49e0 100644 --- a/lib/page/creative_island/creative_island_history_preview.dart +++ b/lib/page/creative_island/creative_island_history_preview.dart @@ -4,8 +4,8 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/creative_island/creative_island_result.dart b/lib/page/creative_island/creative_island_result.dart index 25a1b9f7..7dae1d69 100644 --- a/lib/page/creative_island/creative_island_result.dart +++ b/lib/page/creative_island/creative_island_result.dart @@ -3,8 +3,8 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:circular_countdown_timer/circular_countdown_timer.dart'; diff --git a/lib/page/draw/components/creative_item.dart b/lib/page/creative_island/draw/components/creative_item.dart similarity index 96% rename from lib/page/draw/components/creative_item.dart rename to lib/page/creative_island/draw/components/creative_item.dart index b592cbf4..6c45ecfe 100644 --- a/lib/page/draw/components/creative_item.dart +++ b/lib/page/creative_island/draw/components/creative_item.dart @@ -1,6 +1,6 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/prompt_tags_selector.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class CreativeItem extends StatelessWidget { diff --git a/lib/page/draw/components/image_selector.dart b/lib/page/creative_island/draw/components/image_selector.dart similarity index 99% rename from lib/page/draw/components/image_selector.dart rename to lib/page/creative_island/draw/components/image_selector.dart index 471fb2d6..aae89e75 100644 --- a/lib/page/draw/components/image_selector.dart +++ b/lib/page/creative_island/draw/components/image_selector.dart @@ -4,7 +4,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/draw/components/image_size.dart b/lib/page/creative_island/draw/components/image_size.dart similarity index 94% rename from lib/page/draw/components/image_size.dart rename to lib/page/creative_island/draw/components/image_size.dart index 306cb8a7..fdc27cac 100644 --- a/lib/page/draw/components/image_size.dart +++ b/lib/page/creative_island/draw/components/image_size.dart @@ -1,4 +1,4 @@ -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class ImageSize extends StatelessWidget { diff --git a/lib/page/draw/components/image_style_selector.dart b/lib/page/creative_island/draw/components/image_style_selector.dart similarity index 98% rename from lib/page/draw/components/image_style_selector.dart rename to lib/page/creative_island/draw/components/image_style_selector.dart index 6059b1b6..c17e6e4f 100644 --- a/lib/page/draw/components/image_style_selector.dart +++ b/lib/page/creative_island/draw/components/image_style_selector.dart @@ -2,7 +2,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_input.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/draw/data/draw_history_datasource.dart b/lib/page/creative_island/draw/data/draw_history_datasource.dart similarity index 100% rename from lib/page/draw/data/draw_history_datasource.dart rename to lib/page/creative_island/draw/data/draw_history_datasource.dart diff --git a/lib/page/draw/draw.dart b/lib/page/creative_island/draw/draw.dart similarity index 95% rename from lib/page/draw/draw.dart rename to lib/page/creative_island/draw/draw.dart index f4dd5677..b7d630ae 100644 --- a/lib/page/draw/draw.dart +++ b/lib/page/creative_island/draw/draw.dart @@ -4,9 +4,9 @@ import 'package:askaide/helper/color.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/sliver_component.dart'; -import 'package:askaide/page/draw/components/creative_item.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/draw/components/creative_item.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/draw/draw_create.dart b/lib/page/creative_island/draw/draw_create.dart similarity index 98% rename from lib/page/draw/draw_create.dart rename to lib/page/creative_island/draw/draw_create.dart index 72fb3ec9..f3e2eb98 100644 --- a/lib/page/draw/draw_create.dart +++ b/lib/page/creative_island/draw/draw_create.dart @@ -15,11 +15,11 @@ import 'package:askaide/page/component/prompt_tags_selector.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/draw/components/image_selector.dart'; -import 'package:askaide/page/draw/components/image_size.dart'; -import 'package:askaide/page/draw/components/image_style_selector.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/draw/components/image_selector.dart'; +import 'package:askaide/page/creative_island/draw/components/image_size.dart'; +import 'package:askaide/page/creative_island/draw/components/image_style_selector.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/model/misc.dart'; diff --git a/lib/page/draw/image_edit_direct.dart b/lib/page/creative_island/draw/image_edit_direct.dart similarity index 97% rename from lib/page/draw/image_edit_direct.dart rename to lib/page/creative_island/draw/image_edit_direct.dart index 686bac7f..097fa737 100644 --- a/lib/page/draw/image_edit_direct.dart +++ b/lib/page/creative_island/draw/image_edit_direct.dart @@ -9,9 +9,9 @@ import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/creative_island/content_preview.dart'; import 'package:askaide/page/creative_island/creative_island_result.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/draw/components/image_selector.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/draw/components/image_selector.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/gallery/components/image_card.dart b/lib/page/creative_island/gallery/components/image_card.dart similarity index 98% rename from lib/page/gallery/components/image_card.dart rename to lib/page/creative_island/gallery/components/image_card.dart index 30fc9013..da4a6669 100644 --- a/lib/page/gallery/components/image_card.dart +++ b/lib/page/creative_island/gallery/components/image_card.dart @@ -2,7 +2,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:flutter/material.dart'; class ImageCard extends StatelessWidget { diff --git a/lib/page/gallery/data/gallery_datasource.dart b/lib/page/creative_island/gallery/data/gallery_datasource.dart similarity index 100% rename from lib/page/gallery/data/gallery_datasource.dart rename to lib/page/creative_island/gallery/data/gallery_datasource.dart diff --git a/lib/page/gallery/gallery.dart b/lib/page/creative_island/gallery/gallery.dart similarity index 94% rename from lib/page/gallery/gallery.dart rename to lib/page/creative_island/gallery/gallery.dart index a69f2994..9c3717be 100644 --- a/lib/page/gallery/gallery.dart +++ b/lib/page/creative_island/gallery/gallery.dart @@ -2,10 +2,10 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/sliver_component.dart'; -import 'package:askaide/page/gallery/components/image_card.dart'; -import 'package:askaide/page/gallery/data/gallery_datasource.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/gallery/components/image_card.dart'; +import 'package:askaide/page/creative_island/gallery/data/gallery_datasource.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/creative.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/gallery/gallery_item.dart b/lib/page/creative_island/gallery/gallery_item.dart similarity index 99% rename from lib/page/gallery/gallery_item.dart rename to lib/page/creative_island/gallery/gallery_item.dart index 02e14009..addc76ab 100644 --- a/lib/page/gallery/gallery_item.dart +++ b/lib/page/creative_island/gallery/gallery_item.dart @@ -12,8 +12,8 @@ import 'package:askaide/page/component/gallery_item_share.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index 15fc2433..21250784 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -10,8 +10,8 @@ import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/image_preview.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/image_model.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; diff --git a/lib/page/lab/draw_board.dart b/lib/page/lab/draw_board.dart index 739b3e42..b325dc39 100644 --- a/lib/page/lab/draw_board.dart +++ b/lib/page/lab/draw_board.dart @@ -6,8 +6,8 @@ import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/draw/components/image_selector.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/creative_island/draw/components/image_selector.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:file_saver/file_saver.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/lab/prompt.dart b/lib/page/lab/prompt.dart index 2144e0ab..ddd8c545 100644 --- a/lib/page/lab/prompt.dart +++ b/lib/page/lab/prompt.dart @@ -5,7 +5,7 @@ import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; diff --git a/lib/page/lab/user_center.dart b/lib/page/lab/user_center.dart index 370955de..5edff271 100644 --- a/lib/page/lab/user_center.dart +++ b/lib/page/lab/user_center.dart @@ -5,7 +5,7 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/invite_card.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; diff --git a/lib/page/setting/account_security.dart b/lib/page/setting/account_security.dart index f78228eb..900e692e 100644 --- a/lib/page/setting/account_security.dart +++ b/lib/page/setting/account_security.dart @@ -4,8 +4,8 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/setting/background_selector.dart b/lib/page/setting/background_selector.dart index f8525ea5..77f9ac79 100644 --- a/lib/page/setting/background_selector.dart +++ b/lib/page/setting/background_selector.dart @@ -8,7 +8,7 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/setting/bind_phone_page.dart b/lib/page/setting/bind_phone_page.dart index 192c545f..53a9354d 100644 --- a/lib/page/setting/bind_phone_page.dart +++ b/lib/page/setting/bind_phone_page.dart @@ -9,8 +9,8 @@ import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/verify_code_input.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/setting/change_password.dart b/lib/page/setting/change_password.dart index 47259889..5c99532d 100644 --- a/lib/page/setting/change_password.dart +++ b/lib/page/setting/change_password.dart @@ -6,8 +6,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index e2776348..833dd55f 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -9,8 +9,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/model_indicator.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/setting/destroy_account.dart b/lib/page/setting/destroy_account.dart index b7b69674..87c0f033 100644 --- a/lib/page/setting/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -7,8 +7,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/verify_code_input.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/setting/diagnosis.dart b/lib/page/setting/diagnosis.dart index 810cb4d3..3fad44f6 100644 --- a/lib/page/setting/diagnosis.dart +++ b/lib/page/setting/diagnosis.dart @@ -4,8 +4,8 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; diff --git a/lib/page/setting/openai_setting.dart b/lib/page/setting/openai_setting.dart index 2adade36..5fdd9cbb 100644 --- a/lib/page/setting/openai_setting.dart +++ b/lib/page/setting/openai_setting.dart @@ -8,8 +8,8 @@ import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:dio/dio.dart'; diff --git a/lib/page/setting/retrieve_password_screen.dart b/lib/page/setting/retrieve_password_screen.dart index ca340f50..723a2e8b 100644 --- a/lib/page/setting/retrieve_password_screen.dart +++ b/lib/page/setting/retrieve_password_screen.dart @@ -5,8 +5,8 @@ import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/password_field.dart'; import 'package:askaide/page/component/verify_code_input.dart'; import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:bot_toast/bot_toast.dart'; diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index 70d6833a..ab2d0c57 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -16,9 +16,9 @@ import 'package:askaide/page/component/invite_card.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/sliver_component.dart'; import 'package:askaide/page/component/social_icon.dart'; -import 'package:askaide/page/theme/custom_size.dart'; -import 'package:askaide/page/theme/custom_theme.dart'; -import 'package:askaide/page/theme/theme.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/page/component/theme/theme.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/repo/api_server.dart'; From f7d86e631b19eda3b8c6007945ff64bf15613194 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 12:43:34 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BE=A4=E8=81=8A?= =?UTF-8?q?=E5=A4=B4=E5=83=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/room_bloc.dart | 1 + lib/page/chat/component/group_avatar.dart | 193 +++++++++++ lib/page/chat/component/room_item.dart | 31 +- lib/page/chat/group/create.dart | 377 ++++++---------------- lib/page/chat/group/edit.dart | 8 +- lib/page/chat/rooms.dart | 18 +- lib/page/component/avatar_selector.dart | 54 +++- lib/repo/model/misc.dart | 8 + lib/repo/model/room.dart | 48 +-- pubspec.lock | 8 + pubspec.yaml | 1 + 11 files changed, 423 insertions(+), 324 deletions(-) create mode 100644 lib/page/chat/component/group_avatar.dart diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index d572a040..ef342cc7 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -329,6 +329,7 @@ class RoomBloc extends BlocExt { avatarId: room.avatarId, avatarUrl: room.avatarUrl, roomType: room.roomType, + members: room.members, )) .toList(), suggests: resp.suggests ?? [], diff --git a/lib/page/chat/component/group_avatar.dart b/lib/page/chat/component/group_avatar.dart new file mode 100644 index 00000000..b985f107 --- /dev/null +++ b/lib/page/chat/component/group_avatar.dart @@ -0,0 +1,193 @@ +import 'package:askaide/helper/image.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +class GroupAvatar extends StatelessWidget { + final double size; + final double padding; + final double margin; + final List avatars; + final Color? backgroundColor; + + var row = 0, column = 0; + + GroupAvatar({ + super.key, + this.size = 40, + this.padding = 2, + this.margin = 3, + required this.avatars, + this.backgroundColor, + }); + + @override + Widget build(BuildContext context) { + final avatar = buildAvatar(context); + + return Container( + padding: const EdgeInsets.all(4), + width: size, + height: size, + color: backgroundColor ?? Colors.grey.withAlpha(100), + child: avatar, + ); + } + + double get innerSize => size - 8; + + Widget buildAvatar(BuildContext context) { + var childCount = avatars.length; + int columnMax; + List icons = []; + List stacks = []; + // 五张图片之后(包含5张),每行的最大列数是3 + double imgWidth; + + if (childCount < 2) { + return Container( + width: innerSize, + height: innerSize, + color: Colors.transparent, + ); + } + + if (childCount >= 5) { + columnMax = 3; + imgWidth = (innerSize - (padding * columnMax) - margin) / columnMax; + } else { + columnMax = 2; + imgWidth = (innerSize - (padding * columnMax) - margin) / columnMax; + } + for (var i = 0; i < childCount; i++) { + icons.add(_weChatGroupChatChildIcon(avatars[i], imgWidth)); + } + row = 0; + column = 0; + var centerTop = 0.0; + if (childCount == 2 || childCount == 5 || childCount == 6) { + centerTop = imgWidth / 2; + } + for (var i = 0; i < childCount; i++) { + var left = imgWidth * row + padding * (row + 1); + var top = imgWidth * column + margin * column + centerTop; + switch (childCount) { + case 3: + case 7: + _topOneIcon(stacks, icons[i], childCount, i, imgWidth, left, top); + break; + case 5: + case 8: + _topTwoIcon(stacks, icons[i], childCount, i, imgWidth, left, top); + break; + default: + _otherIcon( + stacks, icons[i], childCount, i, imgWidth, left, top, columnMax); + break; + } + } + + return Container( + width: innerSize, + height: innerSize, + color: Colors.transparent, + padding: EdgeInsets.only(top: padding), + alignment: AlignmentDirectional.bottomCenter, + child: Stack( + children: stacks, + ), + ); + } + + _weChatGroupChatChildIcon(String avatar, double width) { + return ClipRRect( + borderRadius: BorderRadius.circular(2), + child: CachedNetworkImage( + imageUrl: imageURL(avatar, 'avatar'), + height: width, + width: width, + fit: BoxFit.fill, + ), + ); + } + + // 顶部为一张图片 + _topOneIcon(List stacks, Widget child, int childCount, i, imgWidth, + left, top) { + if (i == 0) { + var firstLeft = imgWidth / 2 + left + margin / 2; + if (childCount == 7) { + firstLeft = imgWidth + left + margin; + } + stacks.add(Positioned( + left: firstLeft, + child: child, + )); + row = 0; + // 换行 + column++; + } else { + stacks.add(Positioned( + left: left, + top: top, + child: child, + )); + // 换列 + row++; + if (i == 3) { + // 第一例 + row = 0; + // 换行 + column++; + } + } + } + +// 顶部为两张图片 + _topTwoIcon(List stacks, Widget child, int childCount, i, imgWidth, + left, top) { + if (i == 0 || i == 1) { + stacks.add(Positioned( + left: imgWidth / 2 + left + margin / 2, + top: childCount == 5 ? top : 0.0, + child: child, + )); + row++; + if (i == 1) { + row = 0; + // 换行 + column++; + } + } else { + stacks.add(Positioned( + left: left, + top: top, + child: child, + )); + // 换列 + row++; + if (i == 4) { + // 第一例 + row = 0; + // 换行 + column++; + } + } + } + + _otherIcon(List stacks, Widget child, int childCount, i, imgWidth, + left, top, columnMax) { + stacks.add(Positioned( + left: left, + top: top, + child: child, + )); + // 换列 + row++; + if ((i + 1) % columnMax == 0) { + // 第一例 + row = 0; + // 换行 + column++; + } + } +} diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index f5cb446b..8e641928 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -5,6 +5,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/chat/component/group_avatar.dart'; import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; @@ -12,6 +13,7 @@ import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/room.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_initicon/flutter_initicon.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:go_router/go_router.dart'; @@ -37,7 +39,7 @@ class RoomItem extends StatelessWidget { children: [ const SizedBox(width: 10), SlidableAction( - label: '编辑', + label: '设置', backgroundColor: Colors.green, borderRadius: room.category == 'system' ? BorderRadius.all( @@ -47,7 +49,7 @@ class RoomItem extends StatelessWidget { bottomLeft: Radius.circular(customColors.borderRadius ?? 8), ), - icon: Icons.edit, + icon: Icons.settings, onPressed: (_) { final chatRoomBloc = context.read(); final redirectUrl = room.roomType == 4 @@ -206,6 +208,11 @@ class RoomItem extends StatelessWidget { } Widget _buildAvatar(Room room) { + if (room.members.length == 1 && + (room.avatarUrl == null || room.avatarUrl == '')) { + room.avatarUrl = room.members[0]; + } + if (room.avatarUrl != null && room.avatarUrl!.startsWith('http')) { return SizedBox( width: 70, @@ -222,11 +229,23 @@ class RoomItem extends StatelessWidget { ); } - return RandomAvatar( - id: room.avatar, + if (room.members.isNotEmpty) { + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), + ), + child: GroupAvatar( + size: 70, + avatars: room.members, + ), + ); + } + + return Initicon( + text: room.name.split('、').join(' '), size: 70, - usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + backgroundColor: Colors.grey.withAlpha(100), borderRadius: const BorderRadius.only( topLeft: Radius.circular(8), bottomLeft: Radius.circular(8), diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index 520a74b6..2cfa035e 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -40,13 +40,6 @@ class GroupCreatePage extends StatefulWidget { } class _GroupCreatePageState extends State { - final _nameController = TextEditingController(text: ''); - - String? _avatarUrl; - List avatarPresets = []; - - final randomSeed = Random().nextInt(10000); - List models = []; List selectedModels = []; @@ -56,11 +49,6 @@ class _GroupCreatePageState extends State { void initState() { super.initState(); - // 加载预定义头像 - APIServer().avatars().then((value) { - avatarPresets = value; - }); - // 加载模型 APIServer().models().then((value) { setState(() { @@ -69,12 +57,6 @@ class _GroupCreatePageState extends State { }); } - @override - void dispose() { - _nameController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; @@ -107,241 +89,76 @@ class _GroupCreatePageState extends State { } } }, - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 10), + child: Container( + padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 10), - ColumnBlock( - children: [ - // 名称 - EnhancedTextField( - customColors: customColors, - controller: _nameController, - maxLength: 50, - maxLines: 1, - showCounter: false, - labelText: '群组名称', - labelPosition: LabelPosition.left, - hintText: AppLocale.required.getString(context), - ), - EnhancedInput( - padding: const EdgeInsets.only(top: 10, bottom: 5), - title: Text( - '群组头像', - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 45, - height: 45, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - image: _avatarUrl == null - ? null - : DecorationImage( - image: (_avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced( - _avatarUrl!) - : FileImage(File( - _avatarUrl!))) as ImageProvider, - fit: BoxFit.cover, - ), - ), - child: _avatarUrl == null - ? const Center( - child: Icon( - Icons.interests, - color: Colors.grey, - ), - ) - : const SizedBox(), - ), - ], - ), - onPressed: () { - openModalBottomSheet( - context, - (context) { - return AvatarSelector( - onSelected: (selected) { - setState(() { - _avatarUrl = selected.url; - }); - context.pop(); - }, - usage: AvatarUsage.room, - randomSeed: randomSeed, - defaultAvatarUrl: _avatarUrl, - externalAvatarUrls: [ - ...avatarPresets, - ], - ); - }, - heightFactor: 0.8, - ); - }, + Container( + padding: const EdgeInsets.only(left: 20, bottom: 5), + child: Text( + '选择参与群聊的成员', + style: TextStyle( + fontSize: 14, + color: customColors.weakLinkColor, ), - ], + ), ), - ColumnBlock( - children: [ - // 成员 - EnhancedInput( - padding: const EdgeInsets.only(top: 10, bottom: 5), - title: Text( - '模型成员', - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Stack( + Expanded( + child: ListView.separated( + itemCount: models.length, + itemBuilder: (context, i) { + var item = models[i]; + return ListTile( + title: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, children: [ - Container( - width: - resolveSelectedModelsPreviewWidth(context), - height: 45, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), + _buildAvatar(avatarUrl: item.avatarUrl, size: 40), + Expanded( + child: Container( alignment: Alignment.center, - clipBehavior: Clip.hardEdge, - child: buildSelectedModelsPreview(), - ), - Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: customColors.tagsBackground, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - selectedModels.isEmpty - ? '全部' - : 'x${selectedModels.length}', - style: TextStyle( - fontSize: 8, - color: customColors.weakTextColor, - ), - ), + child: Text(item.shortName), + )), + SizedBox( + width: 10, + child: Icon( + Icons.check, + color: selectedModels.contains(item) + ? customColors.linkColor + : Colors.transparent, ), - ) + ), ], ), - ], - ), - onPressed: () { - openModalBottomSheet( - context, - (context) { - return MultiItemSelector( - itemBuilder: (item) { - return Text(item.shortName); - }, - items: models, - onChanged: (selected) { - setState(() { - selectedModels = selected; - }); - }, - itemAvatarBuilder: (item) { - return _buildAvatar( - avatarUrl: item.avatarUrl, size: 40); - }, - selectedItems: selectedModels, - ); - }, - heightFactor: 0.6, - title: '选择模型', - ); - }, - ), - ], - ), - const SizedBox(height: 10), - Row( - children: [ - Expanded( - child: EnhancedButton( - title: AppLocale.save.getString(context), - onPressed: () async { - globalLoadingCancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return LoadingIndicator( - message: - AppLocale.processingWait.getString(context), - ); - }, - allowClick: false, - duration: const Duration(seconds: 120), - ); - - final name = _nameController.text.trim(); - if (name == '') { - globalLoadingCancel?.call(); - showErrorMessage('请输入群组名称'); - return; - } - - try { - if (_avatarUrl != null) { - if (!(_avatarUrl!.startsWith('http://') || - _avatarUrl!.startsWith('https://'))) { - // 上传文件,获取 URL - final cancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return const LoadingIndicator( - message: "正在上传图片,请稍后...", - ); - }, - allowClick: false, - ); - - final uploadRes = - await ImageUploader(widget.setting) - .upload(_avatarUrl!, usage: 'avatar') - .whenComplete(() => cancel()); - _avatarUrl = uploadRes.url; - } - } - - if (context.mounted) { - context.read().add( - GroupRoomCreateEvent( - name: name, - avatarUrl: _avatarUrl, - members: selectedModels - .map((e) => GroupMember( - modelId: e.realModelId, - modelName: e.shortName)) - .toList(), - ), - ); + ), + onTap: () { + setState(() { + if (selectedModels.contains(item)) { + selectedModels.remove(item); + } else { + selectedModels.add(item); } - } catch (e) { - globalLoadingCancel?.call(); - // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, e); - } + }); }, - ), - ), - ], + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: customColors.columnBlockDividerColor, + ); + }, + ), ), + + const SizedBox(height: 10), + // 保存按钮 + buildSaveButton(context), ], ), ), @@ -350,32 +167,55 @@ class _GroupCreatePageState extends State { ); } - Widget buildSelectedModelsPreview() { - if (selectedModels.isEmpty) { - return const Center( - child: Icon( - Icons.group, - color: Colors.grey, - ), - ); - } - - return Stack( - clipBehavior: Clip.none, + Widget buildSaveButton(BuildContext context) { + return Row( children: [ - for (var i = 0; i < selectedModels.length; i++) - i == 0 - ? _buildAvatar( - avatarUrl: selectedModels.first.avatarUrl, - size: 30, - ) - : Positioned( - left: i * 15.0, - child: _buildAvatar( - avatarUrl: selectedModels[i].avatarUrl, - size: 30, - ), - ), + Expanded( + child: EnhancedButton( + title: AppLocale.ok.getString(context) + + (selectedModels.isNotEmpty + ? ' (x${selectedModels.length})' + : ''), + backgroundColor: selectedModels.isEmpty ? Colors.grey : null, + onPressed: () async { + if (selectedModels.isEmpty) { + return; + } + + globalLoadingCancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), + ); + + try { + if (context.mounted) { + context.read().add( + GroupRoomCreateEvent( + name: selectedModels + .map((e) => e.shortName) + .take(3) + .join("、"), + members: selectedModels + .map((e) => GroupMember( + modelId: e.realModelId, + modelName: e.shortName)) + .toList(), + ), + ); + } + } catch (e) { + globalLoadingCancel?.call(); + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + } + }, + ), + ), ], ); } @@ -395,11 +235,4 @@ class _GroupCreatePageState extends State { Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } - - double resolveSelectedModelsPreviewWidth(BuildContext context) { - final maxSize = MediaQuery.of(context).size.width - 180; - final expectSize = 45.0 + selectedModels.length * 15; - - return expectSize > maxSize ? maxSize : expectSize; - } } diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 164bc9d5..650c4c32 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -173,14 +173,14 @@ class _GroupEditPageState extends State { maxLength: 50, maxLines: 1, showCounter: false, - labelText: '群组名称', + labelText: '名称', labelPosition: LabelPosition.left, hintText: AppLocale.required.getString(context), ), EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '群组头像', + '头像', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -248,7 +248,7 @@ class _GroupEditPageState extends State { EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '模型成员', + '成员', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -327,7 +327,7 @@ class _GroupEditPageState extends State { ); }, heightFactor: 0.6, - title: '选择模型', + title: '选择参与群聊的成员', ); }, ), diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index 36ff954a..324b9f69 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -89,19 +89,11 @@ class _RoomsPageState extends State { title: '发起群聊', icon: Icons.chat_bubble_outline, onTap: (p0) { - // context - // .push('/group-chat-create') - // .whenComplete(() { - // context.read().add(RoomsLoadEvent()); - // }); - - openModalBottomSheet( - context, - (context) { - return Container(); - }, - heightFactor: 0.8, - ); + context + .push('/group-chat-create') + .whenComplete(() { + context.read().add(RoomsLoadEvent()); + }); }, ) ], diff --git a/lib/page/component/avatar_selector.dart b/lib/page/component/avatar_selector.dart index db3a77d7..5528b276 100644 --- a/lib/page/component/avatar_selector.dart +++ b/lib/page/component/avatar_selector.dart @@ -73,17 +73,49 @@ class _AvatarSelectorState extends State { mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ if (_avatarUrl != null) - SizedBox( - width: 100, - height: 100, - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: _avatarUrl!.startsWith('http') - ? CachedNetworkImageEnhanced( - imageUrl: _avatarUrl!, - ) - : Image.file(File(_avatarUrl!)), - ), + Stack( + children: [ + SizedBox( + width: 100, + height: 100, + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: _avatarUrl!.startsWith('http') + ? CachedNetworkImageEnhanced( + imageUrl: _avatarUrl!, + ) + : Image.file(File(_avatarUrl!)), + ), + ), + Positioned( + right: 5, + top: 5, + child: InkWell( + onTap: () { + setState(() { + _avatarUrl = null; + _avatarId = null; + }); + widget.onSelected(Avatar( + type: AvatarType.network, + url: _avatarUrl, + )); + }, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: customColors.chatRoomBackground, + ), + child: Icon( + Icons.close, + size: 10, + color: customColors.weakTextColor, + ), + ), + ), + ), + ], ), if (_avatarId != null) RandomAvatar(id: _avatarId ?? 0, size: 80, usage: widget.usage), diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index 5882d0d4..a7e41c82 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -178,6 +178,8 @@ class RoomInServer { DateTime? createdAt; DateTime? updatedAt; + List members; + RoomInServer({ required this.id, required this.userId, @@ -196,6 +198,7 @@ class RoomInServer { this.createdAt, this.updatedAt, this.maxTokens, + this.members = const [], }); toJson() => { @@ -216,6 +219,7 @@ class RoomInServer { 'last_active_time': lastActiveTime?.toIso8601String(), 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), + 'members': members, }; static RoomInServer fromJson(Map json) { @@ -241,6 +245,10 @@ class RoomInServer { json['CreatedAt'] != null ? DateTime.parse(json['CreatedAt']) : null, updatedAt: json['UpdatedAt'] != null ? DateTime.parse(json['UpdatedAt']) : null, + members: (json['members'] as List?) + ?.map((e) => e.toString()) + .toList() ?? + [], ); } } diff --git a/lib/repo/model/room.dart b/lib/repo/model/room.dart index 0e74beba..e548d549 100644 --- a/lib/repo/model/room.dart +++ b/lib/repo/model/room.dart @@ -84,24 +84,31 @@ class Room { /// 聊天室最后活跃时间 DateTime? lastActiveTime; - Room(this.name, this.category, - {this.description, - this.id, - this.userId, - this.avatarId, - this.avatarUrl, - this.createdAt, - this.lastActiveTime, - this.iconData, - this.systemPrompt, - this.priority = 0, - this.color, - this.roomType, - this.initMessage, - this.localRoom, - this.maxContext = 10, - this.maxTokens, - this.model = defaultChatModel}); + /// 聊天室成员头像列表 + List members; + + Room( + this.name, + this.category, { + this.description, + this.id, + this.userId, + this.avatarId, + this.avatarUrl, + this.createdAt, + this.lastActiveTime, + this.iconData, + this.systemPrompt, + this.priority = 0, + this.color, + this.roomType, + this.initMessage, + this.localRoom, + this.maxContext = 10, + this.maxTokens, + this.model = defaultChatModel, + this.members = const [], + }); Map toJson() { return { @@ -118,6 +125,7 @@ class Room { 'system_prompt': systemPrompt, 'init_message': initMessage, 'max_context': maxContext, + 'members': members, 'created_at': createdAt?.millisecondsSinceEpoch, 'last_active_time': lastActiveTime?.millisecondsSinceEpoch, }; @@ -140,6 +148,10 @@ class Room { initMessage = map['init_message'] as String?, maxContext = map['max_context'] as int? ?? 10, maxTokens = map['max_tokens'] as int?, + members = (map['members'] as List?) + ?.map((e) => e as String) + .toList() ?? + [], createdAt = DateTime.fromMillisecondsSinceEpoch(map['created_at'] as int? ?? 0), lastActiveTime = DateTime.fromMillisecondsSinceEpoch( diff --git a/pubspec.lock b/pubspec.lock index c3774ab7..bf76dd26 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -585,6 +585,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.3" + flutter_initicon: + dependency: "direct main" + description: + name: flutter_initicon + sha256: "5aeda6b16150cb54a34a048a85f9cfddbf36a92135f769ea998d1394dcb54a0f" + url: "https://pub.dev" + source: hosted + version: "3.0.0+1" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1a57a6dc..66ebaa4d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -115,6 +115,7 @@ dependencies: markdown: ^7.1.1 fetch_api: 1.0.1 web_socket_channel: ^2.4.0 + flutter_initicon: ^3.0.0+1 dev_dependencies: flutter_test: From 6f7ecce5c7035631010cd045728de87a3d57472a Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 15:28:47 +0800 Subject: [PATCH 19/28] =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/helper/model.dart | 1 + lib/page/chat/home_chat.dart | 25 ++- lib/page/chat/room_chat.dart | 9 +- lib/page/component/chat/chat_input.dart | 199 ++++++++++------------ lib/page/component/chat/chat_preview.dart | 10 +- lib/repo/model/model.dart | 2 + 6 files changed, 124 insertions(+), 122 deletions(-) diff --git a/lib/helper/model.dart b/lib/helper/model.dart index d73efc8b..26818ed5 100644 --- a/lib/helper/model.dart +++ b/lib/helper/model.dart @@ -32,6 +32,7 @@ class ModelAggregate { disabled: e.disabled, category: e.category, tag: e.tag, + avatarUrl: e.avatarUrl, ), ) .toList()); diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 4e84009a..34d5d430 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -18,6 +18,7 @@ import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/repo/model/chat_history.dart'; import 'package:askaide/repo/model/message.dart'; import 'package:askaide/repo/model/misc.dart'; import 'package:askaide/repo/model/room.dart'; @@ -379,6 +380,17 @@ class _HomeChatPageState extends State { } final messages = loadedMessages.map((e) { + if (loadedState.chatHistory != null && + loadedState.chatHistory!.model != null) { + final mod = supportModels + .where((e) => e.id == loadedState.chatHistory!.model!) + .firstOrNull; + if (mod != null) { + e.senderName = mod.shortName; + e.avatarUrl = mod.avatarUrl; + } + } + final stateMessage = room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(); @@ -392,7 +404,9 @@ class _HomeChatPageState extends State { scrollController: _scrollController, controller: _chatPreviewController, stateManager: widget.stateManager, - robotAvatar: selectMode ? null : _buildAvatar(room.room), + robotAvatar: selectMode + ? null + : _buildAvatar(room.room, his: loadedState.chatHistory), onDeleteMessage: (id) { handleDeleteMessage(context, id, chatHistoryId: chatId); }, @@ -437,11 +451,18 @@ class _HomeChatPageState extends State { .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } - Widget _buildAvatar(Room room) { + Widget _buildAvatar(Room room, {ChatHistory? his}) { if (room.avatarUrl != null && room.avatarUrl!.startsWith('http')) { return RemoteAvatar(avatarUrl: room.avatarUrl!, size: 30); } + if (his != null && his.model != null) { + var mod = supportModels.where((e) => e.id == his.model!).firstOrNull; + if (mod != null && mod.avatarUrl != null && mod.avatarUrl != '') { + return RemoteAvatar(avatarUrl: mod.avatarUrl!, size: 30); + } + } + return const LocalAvatar(assetName: 'assets/app.png', size: 30); } } diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 70fd2b2c..b00979b1 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -27,6 +27,7 @@ import 'package:askaide/repo/model/room.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_initicon/flutter_initicon.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; @@ -381,11 +382,11 @@ class _RoomChatPageState extends State { ); } - return RandomAvatar( - id: room.avatar, + return Initicon( + text: room.name.split('、').join(' '), size: 30, - usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + backgroundColor: Colors.grey.withAlpha(100), + borderRadius: BorderRadius.circular(8), ); } diff --git a/lib/page/component/chat/chat_input.dart b/lib/page/component/chat/chat_input.dart index 8b2d1214..58233c69 100644 --- a/lib/page/component/chat/chat_input.dart +++ b/lib/page/component/chat/chat_input.dart @@ -50,7 +50,7 @@ class _ChatInputState extends State { late final FocusNode _focusNode = FocusNode( onKey: (node, event) { if (!event.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { - if (event is RawKeyDownEvent) { + if (event is RawKeyDownEvent && widget.enableNotifier.value) { _handleSubmited(_textController.text.trim()); } @@ -94,128 +94,100 @@ class _ChatInputState extends State { padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8), decoration: BoxDecoration( color: customColors.backgroundColor, - // borderRadius: const BorderRadius.only( - // topLeft: Radius.circular(10), - // topRight: Radius.circular(10), - // ), - // boxShadow: const [ - // BoxShadow( - // color: Color.fromARGB(31, 161, 161, 161), - // blurRadius: 5, - // spreadRadius: 0, - // offset: Offset(0, -5), - // ), - // ], ), child: Builder(builder: (context) { final setting = context.read(); - if (widget.enableNotifier.value) { - return Column( - children: [ - // 工具栏 - if (widget.toolbar != null) - Row( - children: [ - Expanded(child: widget.toolbar!), - Text( - "${_textController.text.length}/$maxLength", - textScaleFactor: 0.8, - style: TextStyle( - color: customColors.chatInputPanelText, - ), + return Column( + children: [ + // 工具栏 + if (widget.toolbar != null) + Row( + children: [ + Expanded(child: widget.toolbar!), + Text( + "${_textController.text.length}/$maxLength", + textScaleFactor: 0.8, + style: TextStyle( + color: customColors.chatInputPanelText, ), - ], - ), - // if (widget.toolbar != null) - const SizedBox(height: 8), - // 聊天内容输入栏 - SingleChildScrollView( - child: Slidable( - startActionPane: widget.onNewChat != null - ? ActionPane( - extentRatio: 0.3, - motion: const ScrollMotion(), - children: [ - SlidableAction( - autoClose: true, - label: AppLocale.newChat.getString(context), - backgroundColor: Colors.blue, - borderRadius: - const BorderRadius.all(Radius.circular(20)), - onPressed: (_) { - widget.onNewChat!(); - }, - ), - const SizedBox(width: 10), - ], - ) - : null, - child: Row( - children: [ - // 聊天功能按钮 - Row( + ), + ], + ), + // if (widget.toolbar != null) + const SizedBox(height: 8), + // 聊天内容输入栏 + SingleChildScrollView( + child: Slidable( + startActionPane: widget.onNewChat != null + ? ActionPane( + extentRatio: 0.3, + motion: const ScrollMotion(), children: [ - if (widget.enableImageUpload && - Ability().supportImageUploader()) - _buildImageUploadButton( - context, setting, customColors), - if (widget.leftSideToolsBuilder != null) - ...widget.leftSideToolsBuilder!(), - ], - ), - // 聊天输入框 - Expanded( - child: Container( - decoration: BoxDecoration( - color: customColors.chatInputAreaBackground, - borderRadius: BorderRadius.circular(20), + SlidableAction( + autoClose: true, + label: AppLocale.newChat.getString(context), + backgroundColor: Colors.blue, + borderRadius: + const BorderRadius.all(Radius.circular(20)), + onPressed: (_) { + widget.onNewChat!(); + }, ), - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - children: [ - Expanded( - child: TextFormField( - enabled: widget.enableNotifier.value, - keyboardType: TextInputType.multiline, - textInputAction: TextInputAction.newline, - maxLines: 5, - minLines: 1, - maxLength: maxLength, - focusNode: _focusNode, - controller: _textController, - // onSubmitted: _handleSubmited, - decoration: InputDecoration( - hintText: widget.hintText, - hintStyle: const TextStyle( - fontSize: CustomSize.defaultHintTextSize, - ), - border: InputBorder.none, - counterText: '', + const SizedBox(width: 10), + ], + ) + : null, + child: Row( + children: [ + // 聊天功能按钮 + Row( + children: [ + if (widget.enableImageUpload && + Ability().supportImageUploader()) + _buildImageUploadButton( + context, setting, customColors), + if (widget.leftSideToolsBuilder != null) + ...widget.leftSideToolsBuilder!(), + ], + ), + // 聊天输入框 + Expanded( + child: Container( + decoration: BoxDecoration( + color: customColors.chatInputAreaBackground, + borderRadius: BorderRadius.circular(20), + ), + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + Expanded( + child: TextFormField( + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.newline, + maxLines: 5, + minLines: 1, + maxLength: maxLength, + focusNode: _focusNode, + controller: _textController, + decoration: InputDecoration( + hintText: widget.hintText, + hintStyle: const TextStyle( + fontSize: CustomSize.defaultHintTextSize, ), + border: InputBorder.none, + counterText: '', ), ), - // 聊天发送按钮 - _buildSendOrVoiceButton(context, customColors), - ], - ), + ), + // 聊天发送按钮 + _buildSendOrVoiceButton(context, customColors), + ], ), ), - ], - ), + ), + ], ), ), - ], - ); - } - - /// 回复时加载中效果 - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - LoadingAnimationWidget.flickr( - leftDotColor: const Color.fromARGB(255, 0, 214, 187), - rightDotColor: const Color.fromARGB(255, 243, 133, 0), - size: 40, ), ], ); @@ -228,6 +200,13 @@ class _ChatInputState extends State { BuildContext context, CustomColors customColors, ) { + if (!widget.enableNotifier.value) { + return LoadingAnimationWidget.beat( + color: customColors.linkColor!, + size: 20, + ); + } + return _textController.text == '' ? InkWell( onTap: () { diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 98ff2421..248f160e 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -21,6 +21,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; +import 'package:loading_animation_widget/loading_animation_widget.dart'; class ChatPreview extends StatefulWidget { final List messages; @@ -285,12 +286,9 @@ class _ChatPreviewState extends State { builder: (context) { if (message.statusPending() && message.text.isEmpty) { - return const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - ), + return LoadingAnimationWidget.waveDots( + color: customColors.weakLinkColor!, + size: 25, ); } diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index 15d491d7..190f8243 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -8,6 +8,7 @@ class Model { bool isChatModel = false; bool disabled; String? tag; + String? avatarUrl; Model( this.id, @@ -19,6 +20,7 @@ class Model { this.isChatModel = false, this.disabled = false, this.tag, + this.avatarUrl, }); String uid() { From 124cdca52da2161f1972e0fae5eb668b431303b9 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 17:50:03 +0800 Subject: [PATCH 20/28] update --- lib/lang/lang.dart | 4 +- lib/page/chat/component/room_item.dart | 2 - lib/page/chat/group/create.dart | 12 +- lib/page/chat/room_chat.dart | 1 - lib/page/component/chat/chat_preview.dart | 1 - lib/page/component/model_item.dart | 194 ++++++---------------- lib/page/lab/creative_models.dart | 3 - lib/page/setting/custom_home_models.dart | 2 +- 8 files changed, 59 insertions(+), 160 deletions(-) diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 2a63fa5c..a8755b73 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -221,7 +221,7 @@ mixin AppLocale { clearChatHistory: '清空聊天记录', examples: '示例', continueMessage: '继续', - messageInputTips: '有问题尽管问我...', + messageInputTips: '有问题尽管问我', uploadImage: '上传图片', longPressSpeak: '长按说话', send: '发送', @@ -312,7 +312,7 @@ mixin AppLocale { promptHint: '设定该数字人的角色和技能,以便为你提供更精准有效的信息。', confirmClearCache: '确定要清除缓存吗?', confirmSignOut: '确定要退出登录吗?', - askMeAnyQuestion: '有问题尽管问我~', + askMeAnyQuestion: '有问题尽管问我', askMeLikeThis: '可以这样问我:', refresh: '换一换', fastAndCostEffective: '速度快,成本低', diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index 8e641928..3014a63f 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -1,5 +1,4 @@ import 'package:askaide/bloc/room_bloc.dart'; -import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; @@ -7,7 +6,6 @@ import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/chat/component/group_avatar.dart'; import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/room.dart'; diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index 2cfa035e..0efa4703 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -1,21 +1,11 @@ -import 'dart:io'; -import 'dart:math'; - import 'package:askaide/bloc/room_bloc.dart'; import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/image.dart'; -import 'package:askaide/helper/upload.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/avatar_selector.dart'; import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/enhanced_button.dart'; -import 'package:askaide/page/component/enhanced_input.dart'; -import 'package:askaide/page/component/enhanced_textfield.dart'; -import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/component/multi_item_selector.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -122,7 +112,7 @@ class _GroupCreatePageState extends State { Expanded( child: Container( alignment: Alignment.center, - child: Text(item.shortName), + child: Text(item.name), )), SizedBox( width: 10, diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index b00979b1..7ce2838b 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -1,5 +1,4 @@ import 'package:askaide/bloc/free_count_bloc.dart'; -import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/image.dart'; diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 248f160e..9783b560 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -9,7 +9,6 @@ import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/attached_button_panel.dart'; import 'package:askaide/page/component/chat/chat_share.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; -import 'package:askaide/page/component/share.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/repo/api_server.dart'; diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 440126c9..ac71df08 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -1,4 +1,7 @@ -import 'package:askaide/page/component/dialog.dart'; +import 'package:askaide/helper/ability.dart'; +import 'package:askaide/helper/constant.dart'; +import 'package:askaide/helper/image.dart'; +import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart'; import 'package:flutter/material.dart'; @@ -18,48 +21,49 @@ class ModelItem extends StatelessWidget { @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; - - Map> modelGroups = {}; - for (var model in models) { - if (modelGroups.containsKey(model.category)) { - modelGroups[model.category]!.add(model); - } else { - modelGroups[model.category] = [model]; - } - } - return models.isNotEmpty - ? ListView( - children: [ - for (var group in modelGroups.entries) - ExpansionTile( - title: Row(children: [ - Text( - group.key.toUpperCase(), - style: TextStyle( - color: customColors.weakLinkColor, - ), - ), - if (group.value.where((e) => !e.disabled).isEmpty) - Text( - '(敬请期待)', - style: TextStyle( - color: customColors.weakTextColor, + ? ListView.separated( + itemCount: models.length, + itemBuilder: (context, i) { + var item = models[i]; + return ListTile( + title: Container( + alignment: Alignment.center, + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _buildAvatar(avatarUrl: item.avatarUrl, size: 40), + Expanded( + child: Container( + alignment: Alignment.center, + child: Text(item.name), + )), + SizedBox( + width: 10, + child: Icon( + Icons.check, + color: initValue == item.uid() || initValue == item.id + ? customColors.linkColor + : Colors.transparent, ), - textScaleFactor: 0.7, ), - ]), - iconColor: customColors.weakLinkColor, - childrenPadding: const EdgeInsets.only(bottom: 10), - initiallyExpanded: group.value - .where((e) => e.uid() == initValue || e.id == initValue) - .isNotEmpty, - children: [ - for (var model in group.value) - _buildListTile(model, customColors, context), - ], + ], + ), ), - ], + onTap: () { + onSelected(item); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: customColors.columnBlockDividerColor, + ); + }, ) : const Center( child: Text( @@ -69,107 +73,19 @@ class ModelItem extends StatelessWidget { ); } - ListTile _buildListTile( - Model model, - CustomColors customColors, - BuildContext context, - ) { - return ListTile( - title: Stack( - children: [ - Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 10, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: initValue == model.uid() || initValue == model.id - ? customColors.linkColor - : (model.disabled - ? customColors.tagsBackground - : customColors.tagsBackgroundHover), - boxShadow: [ - BoxShadow( - color: customColors.boxShadowColor!, - blurRadius: 3, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - Text( - // 模型 ID - model.name, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: initValue == model.uid() || initValue == model.id - ? Colors.white - : (model.disabled - ? customColors.weakTextColor!.withAlpha(150) - : customColors.chatExampleItemText), - ), - ), - // 模型描述 - if (model.description != null) const SizedBox(height: 5), - if (model.description != null) - Text( - model.description!, - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: initValue == model.uid() || - initValue == model.id - ? Colors.white - : (model.disabled - ? customColors.weakTextColor!.withAlpha(150) - : customColors.chatExampleItemText), - ), - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - maxLines: 3, - ), - ], - ), - ), - if (model.disabled) - _buildBadge(const Color.fromARGB(150, 122, 122, 122), '敬请期待'), - if (!model.disabled && model.tag != null) - _buildBadge(const Color.fromARGB(150, 122, 122, 122), model.tag!), - ], - ), - onTap: () { - if (model.disabled) { - showImportantMessage(context, '该模型即将推出,敬请期待!'); - return; - } - onSelected(model); - }, - ); - } + Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { + if (avatarUrl != null && avatarUrl.startsWith('http')) { + return RemoteAvatar( + avatarUrl: imageURL(avatarUrl, qiniuImageTypeAvatar), + size: size, + ); + } - Widget _buildBadge(Color color, String text) { - return Positioned( - top: 0, - left: 0, - child: Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - bottomRight: Radius.circular(10), - ), - color: color, - ), - child: Text( - text, - style: const TextStyle( - fontSize: 10, - color: Colors.white, - ), - ), - ), + return RandomAvatar( + id: id ?? 0, + size: size, + usage: + Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } } diff --git a/lib/page/lab/creative_models.dart b/lib/page/lab/creative_models.dart index 21250784..a099a347 100644 --- a/lib/page/lab/creative_models.dart +++ b/lib/page/lab/creative_models.dart @@ -1,7 +1,5 @@ import 'package:askaide/bloc/creative_island_bloc.dart'; import 'package:askaide/helper/constant.dart'; -import 'package:askaide/helper/helper.dart'; -import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; @@ -20,7 +18,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; -import 'package:sizer/sizer.dart'; class CreativeModelScreen extends StatefulWidget { final SettingRepository setting; diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index 833dd55f..10913c61 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -58,7 +58,7 @@ class _CustomHomeModelsPageState extends State { .toList(); } - APIServer().capabilities().then((cap) { + APIServer().capabilities(cache: false).then((cap) { Ability().updateCapabilities(cap); if (cap.homeModels.isNotEmpty) { From bfa2fc94d28faa76d8be7e1505af3837e1382a81 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Thu, 26 Oct 2023 18:02:16 +0800 Subject: [PATCH 21/28] update --- lib/page/chat/group/create.dart | 92 +++++++++++++++++---------------- lib/page/chat/group/edit.dart | 2 +- 2 files changed, 48 insertions(+), 46 deletions(-) diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index 0efa4703..7062881d 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -158,55 +158,57 @@ class _GroupCreatePageState extends State { } Widget buildSaveButton(BuildContext context) { - return Row( - children: [ - Expanded( - child: EnhancedButton( - title: AppLocale.ok.getString(context) + - (selectedModels.isNotEmpty - ? ' (x${selectedModels.length})' - : ''), - backgroundColor: selectedModels.isEmpty ? Colors.grey : null, - onPressed: () async { - if (selectedModels.isEmpty) { - return; - } + return SafeArea( + child: Row( + children: [ + Expanded( + child: EnhancedButton( + title: AppLocale.ok.getString(context) + + (selectedModels.isNotEmpty + ? ' (x${selectedModels.length})' + : ''), + backgroundColor: selectedModels.isEmpty ? Colors.grey : null, + onPressed: () async { + if (selectedModels.isEmpty) { + return; + } - globalLoadingCancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return LoadingIndicator( - message: AppLocale.processingWait.getString(context), - ); - }, - allowClick: false, - duration: const Duration(seconds: 120), - ); + globalLoadingCancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), + ); - try { - if (context.mounted) { - context.read().add( - GroupRoomCreateEvent( - name: selectedModels - .map((e) => e.shortName) - .take(3) - .join("、"), - members: selectedModels - .map((e) => GroupMember( - modelId: e.realModelId, - modelName: e.shortName)) - .toList(), - ), - ); + try { + if (context.mounted) { + context.read().add( + GroupRoomCreateEvent( + name: selectedModels + .map((e) => e.shortName) + .take(3) + .join("、"), + members: selectedModels + .map((e) => GroupMember( + modelId: e.realModelId, + modelName: e.shortName)) + .toList(), + ), + ); + } + } catch (e) { + globalLoadingCancel?.call(); + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); } - } catch (e) { - globalLoadingCancel?.call(); - // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, e); - } - }, + }, + ), ), - ), - ], + ], + ), ); } diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 650c4c32..3e4b7267 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -326,7 +326,7 @@ class _GroupEditPageState extends State { selectedItems: selectedModels, ); }, - heightFactor: 0.6, + heightFactor: 0.8, title: '选择参与群聊的成员', ); }, From 48bebbb3405f39761a89dcf1bf7bfaf072f82d40 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Fri, 27 Oct 2023 01:37:39 +0800 Subject: [PATCH 22/28] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/chat_message_bloc.dart | 5 + lib/lang/lang.dart | 9 +- lib/page/chat/group/chat.dart | 85 ++++---- lib/page/chat/group/create.dart | 224 ++++++++++---------- lib/page/chat/group/edit.dart | 67 ++++-- lib/page/chat/home.dart | 5 +- lib/page/chat/room_create.dart | 23 +- lib/page/chat/room_edit.dart | 22 +- lib/page/component/chat/chat_preview.dart | 3 +- lib/page/component/enhanced_input.dart | 2 +- lib/page/component/model_item.dart | 84 ++++---- lib/page/component/multi_item_selector.dart | 159 +++++++------- lib/page/setting/custom_home_models.dart | 2 +- 13 files changed, 360 insertions(+), 330 deletions(-) diff --git a/lib/bloc/chat_message_bloc.dart b/lib/bloc/chat_message_bloc.dart index 69607d91..a64657dd 100644 --- a/lib/bloc/chat_message_bloc.dart +++ b/lib/bloc/chat_message_bloc.dart @@ -357,6 +357,11 @@ class ChatMessageBloc extends BlocExt { await listener; + waitMessage.text = waitMessage.text.trim(); + if (waitMessage.text.isEmpty) { + error = RequestFailedException('机器人没有回答任何内容', 500); + } + if (error != null) { throw error!; } diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index a8755b73..5ac6fc83 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -84,6 +84,7 @@ mixin AppLocale { static const String quotaExceeded = 'quota-exceeded'; static const String internalServerError = 'internal-server-error'; static const String badGateway = 'bad-gateway'; + static const String emptyResponse = 'empty-response'; static const String modelNotValid = 'model-not-valid'; static const String signInRequired = 'sign-in-required'; static const String accountNeedReSignin = 'account-need-re-signin'; @@ -266,7 +267,7 @@ mixin AppLocale { search: '搜索', background: '背景', backgroundSetting: '背景图', - roomSetting: '数字人设置', + roomSetting: '设置', chatHistory: '聊天记录', confirmSend: '确定发送以下内容?', questionExamples: '问题示例', @@ -280,7 +281,7 @@ mixin AppLocale { modelRequiredMessage: '请选择 AI 模型', operateSuccess: '操作成功', operateFailed: '操作失败', - confirmDelete: '确定要删除这些项目?', + confirmDelete: '确定删除?', confirmStartNewChat: '确定要开始新的对话?', confirmClearMessages: '确定要清空聊天记录?', quotaExceeded: '智慧果数量不足,请先购买', @@ -290,7 +291,7 @@ mixin AppLocale { signInRequired: '您尚未登录,请先登录', accountNeedReSignin: '账号异常,请重新登录', openAIAuthFailed: '您启用了自定义 OpenAI 服务,请检查 API Key 是否正确', - confirmToDeleteRoom: '确定删除该数字人?', + confirmToDeleteRoom: '确定删除?', writeYourIdeas: '你的想法', describeYourImages: '你的想法', excludeContents: '反向提示词', @@ -458,7 +459,7 @@ mixin AppLocale { search: 'Search', background: 'Background', backgroundSetting: 'Background Setting', - roomSetting: 'Character Setting', + roomSetting: 'Setting', chatHistory: 'Histories', confirmSend: 'Confirm to send?', questionExamples: 'Question Examples', diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index e0529297..a68659cb 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -187,8 +187,11 @@ class _GroupChatPageState extends State { padding: const EdgeInsets.all(5), child: InkWell( onTap: () { - onModelSelect(context, groupState, - customColors); + onModelSelect( + context, + groupState, + customColors, + ); }, child: Icon( Icons.alternate_email, @@ -240,28 +243,46 @@ class _GroupChatPageState extends State { openModalBottomSheet( context, (context) { - return MultiItemSelector( - itemBuilder: (item) { - return Text(item.modelName); - }, - items: groupState.group.members.where((e) => e.status != 2).toList(), - onChanged: (selected) { - setState(() { - selectedMembers = selected; - }); - }, - itemAvatarBuilder: (item) { - return _buildAvatar( - avatarUrl: item.avatarUrl, - id: item.id, - size: 30, - ); - }, - selectedItems: selectedMembers, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.only(top: 15, left: 20), + child: Text( + '选择本次对话成员', + style: TextStyle( + fontSize: 14, + color: customColors.weakLinkColor, + ), + ), + ), + Expanded( + child: MultiItemSelector( + itemBuilder: (item) { + return Text(item.modelName); + }, + items: groupState.group.members + .where((e) => e.status != 2) + .toList(), + onChanged: (selected) { + setState(() { + selectedMembers = selected; + }); + }, + itemAvatarBuilder: (item) { + return _buildAvatar( + avatarUrl: item.avatarUrl, + id: item.id, + size: 30, + ); + }, + selectedItems: selectedMembers, + ), + ), + ], ); }, heightFactor: 0.6, - title: '选择对话的模型', ); } @@ -408,28 +429,6 @@ class _GroupChatPageState extends State { ); } - Widget buildMembersBar(List members) { - return Container( - height: 30, - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - child: ListView( - scrollDirection: Axis.horizontal, - shrinkWrap: true, - children: [ - for (var member in members) - Container( - margin: const EdgeInsets.symmetric(horizontal: 5), - child: _buildAvatar( - avatarUrl: member.avatarUrl, - id: member.id, - size: 20, - ), - ), - ], - ), - ); - } - /// 构建 AppBar AppBar _buildAppBar(BuildContext context, CustomColors customColors) { return _chatPreviewController.selectMode diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index 7062881d..c7d632f6 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -59,6 +59,24 @@ class _GroupCreatePageState extends State { centerTitle: true, elevation: 0, toolbarHeight: CustomSize.toolbarHeight, + actions: [ + Padding( + padding: const EdgeInsets.only(right: 12), + child: EnhancedButton( + width: 50, + height: 30, + fontSize: 14, + title: AppLocale.ok.getString(context), + color: selectedModels.isEmpty ? customColors.weakTextColor : null, + backgroundColor: selectedModels.isEmpty + ? customColors.weakTextColor!.withAlpha(20) + : null, + onPressed: () { + onSave(context); + }, + ), + ), + ], ), backgroundColor: customColors.backgroundContainerColor, body: BackgroundContainer( @@ -79,137 +97,109 @@ class _GroupCreatePageState extends State { } } }, - child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - padding: const EdgeInsets.only(left: 20, bottom: 5), - child: Text( - '选择参与群聊的成员', - style: TextStyle( - fontSize: 14, - color: customColors.weakLinkColor, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.only(top: 15, left: 20, bottom: 15), + child: Text( + '选择参与群聊的成员', + style: TextStyle( + fontSize: 14, + color: customColors.weakLinkColor, ), ), - Expanded( - child: ListView.separated( - itemCount: models.length, - itemBuilder: (context, i) { - var item = models[i]; - return ListTile( - title: Container( - alignment: Alignment.center, - padding: const EdgeInsets.symmetric( - horizontal: 10, vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - _buildAvatar(avatarUrl: item.avatarUrl, size: 40), - Expanded( - child: Container( - alignment: Alignment.center, + ), + Expanded( + child: ListView.separated( + itemCount: models.length, + itemBuilder: (context, i) { + var item = models[i]; + return CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + checkboxShape: const CircleBorder(), + activeColor: customColors.linkColor, + side: BorderSide( + color: customColors.weakTextColor!.withAlpha(100), + ), + title: Container( + alignment: Alignment.center, + padding: const EdgeInsets.symmetric(vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _buildAvatar(avatarUrl: item.avatarUrl, size: 40), + const SizedBox(width: 20), + Expanded( + child: Container( + alignment: Alignment.centerLeft, child: Text(item.name), - )), - SizedBox( - width: 10, - child: Icon( - Icons.check, - color: selectedModels.contains(item) - ? customColors.linkColor - : Colors.transparent, - ), ), - ], - ), + ), + ], ), - onTap: () { - setState(() { - if (selectedModels.contains(item)) { - selectedModels.remove(item); - } else { - selectedModels.add(item); - } - }); - }, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider( - height: 1, - color: customColors.columnBlockDividerColor, - ); - }, - ), + ), + onChanged: (selected) { + setState(() { + if (selectedModels.contains(item)) { + selectedModels.remove(item); + } else { + selectedModels.add(item); + } + }); + }, + value: selectedModels.contains(item), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: + customColors.columnBlockDividerColor?.withAlpha(200), + ); + }, ), - - const SizedBox(height: 10), - // 保存按钮 - buildSaveButton(context), - ], - ), + ), + ], ), ), ), ); } - Widget buildSaveButton(BuildContext context) { - return SafeArea( - child: Row( - children: [ - Expanded( - child: EnhancedButton( - title: AppLocale.ok.getString(context) + - (selectedModels.isNotEmpty - ? ' (x${selectedModels.length})' - : ''), - backgroundColor: selectedModels.isEmpty ? Colors.grey : null, - onPressed: () async { - if (selectedModels.isEmpty) { - return; - } - - globalLoadingCancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return LoadingIndicator( - message: AppLocale.processingWait.getString(context), - ); - }, - allowClick: false, - duration: const Duration(seconds: 120), - ); + void onSave(BuildContext context) { + if (selectedModels.isEmpty) { + return; + } - try { - if (context.mounted) { - context.read().add( - GroupRoomCreateEvent( - name: selectedModels - .map((e) => e.shortName) - .take(3) - .join("、"), - members: selectedModels - .map((e) => GroupMember( - modelId: e.realModelId, - modelName: e.shortName)) - .toList(), - ), - ); - } - } catch (e) { - globalLoadingCancel?.call(); - // ignore: use_build_context_synchronously - showErrorMessageEnhanced(context, e); - } - }, - ), - ), - ], - ), + globalLoadingCancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 120), ); + + try { + if (context.mounted) { + context.read().add( + GroupRoomCreateEvent( + name: selectedModels.map((e) => e.shortName).take(3).join("、"), + members: selectedModels + .map((e) => GroupMember( + modelId: e.realModelId, modelName: e.shortName)) + .toList(), + ), + ); + } + } catch (e) { + globalLoadingCancel?.call(); + // ignore: use_build_context_synchronously + showErrorMessageEnhanced(context, e); + } } Widget _buildAvatar({String? avatarUrl, int? id, int size = 30}) { diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 3e4b7267..4072ec03 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -104,9 +104,9 @@ class _GroupEditPageState extends State { final customColors = Theme.of(context).extension()!; return Scaffold( appBar: AppBar( - title: const Text( - '群聊设置', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), + title: Text( + AppLocale.roomSetting.getString(context), + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, elevation: 0, @@ -176,6 +176,7 @@ class _GroupEditPageState extends State { labelText: '名称', labelPosition: LabelPosition.left, hintText: AppLocale.required.getString(context), + textDirection: TextDirection.rtl, ), EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), @@ -271,26 +272,26 @@ class _GroupEditPageState extends State { clipBehavior: Clip.hardEdge, child: buildSelectedModelsPreview(), ), - Positioned( - right: 0, - top: 0, - child: Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - color: customColors.tagsBackground, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - selectedModels.isEmpty - ? '全部' - : 'x${selectedModels.length}', - style: TextStyle( - fontSize: 8, - color: customColors.weakTextColor, + if (selectedModels.isNotEmpty) + Positioned( + right: 0, + top: 0, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: customColors.tagsBackground, + borderRadius: + BorderRadius.circular(8), + ), + child: Text( + 'x${selectedModels.length}', + style: TextStyle( + fontSize: 8, + color: customColors.weakTextColor, + ), ), ), - ), - ) + ) ], ), ], @@ -301,7 +302,7 @@ class _GroupEditPageState extends State { (context) { return MultiItemSelector( itemBuilder: (item) { - return Text(item.model.shortName); + return Text(item.model.name); }, items: models .map((e) => ModelWithMemberId( @@ -327,7 +328,6 @@ class _GroupEditPageState extends State { ); }, heightFactor: 0.8, - title: '选择参与群聊的成员', ); }, ), @@ -339,7 +339,16 @@ class _GroupEditPageState extends State { Expanded( child: EnhancedButton( title: AppLocale.save.getString(context), + color: + canSubmit() ? null : customColors.weakTextColor, + backgroundColor: canSubmit() + ? null + : customColors.weakTextColor!.withAlpha(20), onPressed: () async { + if (!canSubmit()) { + return; + } + globalLoadingCancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return LoadingIndicator( @@ -416,6 +425,18 @@ class _GroupEditPageState extends State { ); } + bool canSubmit() { + if (selectedModels.isEmpty) { + return false; + } + + if (_nameController.text.trim() == '') { + return false; + } + + return true; + } + Widget buildSelectedModelsPreview() { if (selectedModels.isEmpty) { return const Center( diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index ea70adde..e03f2697 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -259,10 +259,7 @@ class _HomePageState extends State { ), actions: [ IconButton( - icon: Icon( - Icons.history, - color: customColors.backgroundInvertedColor, - ), + icon: const Icon(Icons.history), onPressed: () { context.push('/chat-chat/history').whenComplete(() { context diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 59f7d161..4ab64998 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -55,7 +55,7 @@ class _RoomCreatePageState extends State { List avatarPresets = []; - int maxContext = 5; + int maxContext = 3; List validMemories = [ ChatMemory('无记忆', 1, description: '每次对话都是独立的,常用于一次性问答'), @@ -204,7 +204,7 @@ class _RoomCreatePageState extends State { child: Row( children: [ WeakTextButton( - title: '取消', + title: AppLocale.cancel.getString(context), onPressed: () { selectedSuggestions.clear(); setState(() {}); @@ -213,7 +213,7 @@ class _RoomCreatePageState extends State { const SizedBox(width: 20), Expanded( child: EnhancedButton( - title: '添加为专属伙伴', + title: AppLocale.ok.getString(context), onPressed: () { context.read().add(GalleryRoomCopyEvent( selectedSuggestions.map((e) => e.id).toList())); @@ -282,16 +282,16 @@ class _RoomCreatePageState extends State { maxLength: 50, maxLines: 1, showCounter: false, - labelText: AppLocale.room.getString(context) + - AppLocale.roomName.getString(context), + labelText: AppLocale.roomName.getString(context), labelPosition: LabelPosition.left, hintText: AppLocale.required.getString(context), + textDirection: TextDirection.rtl, ), if (Ability().supportAPIServer()) EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '数字人头像', + '头像', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -460,9 +460,10 @@ class _RoomCreatePageState extends State { ), value: Text( validMemories - .where((element) => element.number == maxContext) - .first - .name, + .where((element) => element.number == maxContext) + .firstOrNull + ?.name ?? + '', ), onPressed: () { openListSelectDialog( @@ -498,7 +499,9 @@ class _RoomCreatePageState extends State { return true; }, heightFactor: 0.5, - value: maxContext, + value: validMemories + .where((element) => element.number == maxContext) + .firstOrNull, ); }, ), diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 569e5e8d..3f731007 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -166,17 +166,17 @@ class _RoomEditPageState extends State { maxLength: 50, maxLines: 1, showCounter: false, - labelText: AppLocale.room.getString(context) + - AppLocale.roomName.getString(context), + labelText: AppLocale.roomName.getString(context), labelPosition: LabelPosition.left, hintText: AppLocale.required.getString(context), + textDirection: TextDirection.rtl, ), if (Ability().supportAPIServer()) EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( - '数字人头像', + '头像', style: TextStyle( color: customColors.textfieldLabelColor, fontSize: 16, @@ -263,7 +263,7 @@ class _RoomEditPageState extends State { // 模型 EnhancedInputSimple( title: AppLocale.model.getString(context), - padding: const EdgeInsets.only(top: 10, bottom: 10), + padding: const EdgeInsets.only(top: 10, bottom: 0), onPressed: () { openSelectModelDialog( context, @@ -347,10 +347,11 @@ class _RoomEditPageState extends State { ), value: Text( validMemories - .where((element) => - element.number == maxContext) - .first - .name, + .where((element) => + element.number == maxContext) + .firstOrNull + ?.name ?? + '', ), onPressed: () { openListSelectDialog( @@ -387,7 +388,10 @@ class _RoomEditPageState extends State { return true; }, heightFactor: 0.5, - value: maxContext, + value: validMemories + .where((element) => + element.number == maxContext) + .firstOrNull, ); }, ), diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 9783b560..2c1ee643 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -283,7 +283,8 @@ class _ChatPreviewState extends State { ), child: Builder( builder: (context) { - if (message.statusPending() && + if ((message.statusPending() || + !message.isReady) && message.text.isEmpty) { return LoadingAnimationWidget.waveDots( color: customColors.weakLinkColor!, diff --git a/lib/page/component/enhanced_input.dart b/lib/page/component/enhanced_input.dart index 79ea5560..624939b9 100644 --- a/lib/page/component/enhanced_input.dart +++ b/lib/page/component/enhanced_input.dart @@ -38,7 +38,7 @@ class EnhancedInputSimple extends StatelessWidget { overflow: TextOverflow.ellipsis, style: TextStyle( color: customColors.textfieldValueColor, - fontSize: 14, + fontSize: 15, ), ) : null, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index ac71df08..a4b3c489 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -22,48 +22,54 @@ class ModelItem extends StatelessWidget { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return models.isNotEmpty - ? ListView.separated( - itemCount: models.length, - itemBuilder: (context, i) { - var item = models[i]; - return ListTile( - title: Container( - alignment: Alignment.center, - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - _buildAvatar(avatarUrl: item.avatarUrl, size: 40), - Expanded( + ? Padding( + padding: const EdgeInsets.only(top: 15), + child: ListView.separated( + itemCount: models.length, + itemBuilder: (context, i) { + var item = models[i]; + return ListTile( + title: Container( + alignment: Alignment.center, + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _buildAvatar(avatarUrl: item.avatarUrl, size: 40), + const SizedBox(width: 20), + Expanded( child: Container( - alignment: Alignment.center, - child: Text(item.name), - )), - SizedBox( - width: 10, - child: Icon( - Icons.check, - color: initValue == item.uid() || initValue == item.id - ? customColors.linkColor - : Colors.transparent, + alignment: Alignment.centerLeft, + child: Text(item.name), + ), ), - ), - ], + SizedBox( + width: 10, + child: Icon( + Icons.check, + color: + initValue == item.uid() || initValue == item.id + ? customColors.linkColor + : Colors.transparent, + ), + ), + ], + ), ), - ), - onTap: () { - onSelected(item); - }, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider( - height: 1, - color: customColors.columnBlockDividerColor, - ); - }, + onTap: () { + onSelected(item); + }, + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: customColors.columnBlockDividerColor, + ); + }, + ), ) : const Center( child: Text( diff --git a/lib/page/component/multi_item_selector.dart b/lib/page/component/multi_item_selector.dart index 16a33ee9..fe866068 100644 --- a/lib/page/component/multi_item_selector.dart +++ b/lib/page/component/multi_item_selector.dart @@ -40,88 +40,91 @@ class _MultiItemSelectorState extends State> { Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; - return Column( - children: [ - if (widget.onSubmit != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - EnhancedButton( - width: 100, - height: 40, - backgroundColor: customColors.weakTextColor, - title: AppLocale.cancel.getString(context), - onPressed: () { - context.pop(); - }, - ), - EnhancedButton( - width: 100, - height: 40, - title: AppLocale.ok.getString(context), - onPressed: () { - widget.onSubmit!(selectedItems); - }, - ), - ], - ), - Expanded( - child: ListView.separated( - itemCount: widget.items.length, - itemBuilder: (context, i) { - var item = widget.items[i]; - return ListTile( - title: Container( - alignment: Alignment.center, - padding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - if (widget.itemAvatarBuilder != null) - widget.itemAvatarBuilder!(item), - Expanded( + return Container( + margin: const EdgeInsets.only(top: 15), + child: Column( + children: [ + if (widget.onSubmit != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + EnhancedButton( + width: 100, + height: 40, + backgroundColor: customColors.weakTextColor, + title: AppLocale.cancel.getString(context), + onPressed: () { + context.pop(); + }, + ), + EnhancedButton( + width: 100, + height: 40, + title: AppLocale.ok.getString(context), + onPressed: () { + widget.onSubmit!(selectedItems); + }, + ), + ], + ), + Expanded( + child: ListView.separated( + itemCount: widget.items.length, + itemBuilder: (context, i) { + var item = widget.items[i]; + return CheckboxListTile( + controlAffinity: ListTileControlAffinity.leading, + checkboxShape: const CircleBorder(), + activeColor: customColors.linkColor, + side: BorderSide( + color: customColors.weakTextColor!.withAlpha(100), + ), + title: Container( + alignment: Alignment.center, + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.itemAvatarBuilder != null) + widget.itemAvatarBuilder!(item), + const SizedBox(width: 20), + Expanded( child: Container( - alignment: Alignment.center, - child: widget.itemBuilder(item), - )), - SizedBox( - width: 10, - child: Icon( - Icons.check, - color: selectedItems.contains(item) - ? customColors.linkColor - : Colors.transparent, + alignment: Alignment.centerLeft, + child: widget.itemBuilder(item), + ), ), - ), - ], + ], + ), ), - ), - onTap: () { - setState(() { - if (selectedItems.contains(item)) { - selectedItems.remove(item); - } else { - selectedItems.add(item); - } + onChanged: (selected) { + setState(() { + if (selectedItems.contains(item)) { + selectedItems.remove(item); + } else { + selectedItems.add(item); + } - if (widget.onChanged != null) { - widget.onChanged!(selectedItems); - } - }); - }, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider( - height: 1, - color: customColors.columnBlockDividerColor, - ); - }, - ), - ) - ], + if (widget.onChanged != null) { + widget.onChanged!(selectedItems); + } + }); + }, + value: selectedItems.contains(item), + ); + }, + separatorBuilder: (BuildContext context, int index) { + return Divider( + height: 1, + color: customColors.columnBlockDividerColor, + ); + }, + ), + ) + ], + ), ); } } diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index 10913c61..51e38856 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -104,7 +104,7 @@ class _CustomHomeModelsPageState extends State { child: Column( children: [ const MessageBox( - message: '用于设置聊一聊功能所选用的常用模型。', + message: '用于设置聊一聊中的常用模型。', type: MessageBoxType.info, ), const SizedBox(height: 10), From bde4981457f732aacbdd77afdfdee6501142543c Mon Sep 17 00:00:00 2001 From: mylxsw Date: Fri, 27 Oct 2023 17:29:08 +0800 Subject: [PATCH 23/28] update --- lib/helper/ability.dart | 4 + lib/page/chat/component/group_empty.dart | 107 +++++++++++++++++++++ lib/page/chat/group/chat.dart | 9 ++ lib/page/chat/group/create.dart | 2 +- lib/page/chat/group/edit.dart | 8 ++ lib/page/chat/room_chat.dart | 2 +- lib/page/chat/room_create.dart | 7 +- lib/page/chat/room_edit.dart | 5 + lib/page/component/account_quota_card.dart | 2 +- lib/page/component/chat/chat_share.dart | 78 +++++++-------- lib/page/component/gallery_item_share.dart | 78 +++++++-------- lib/page/setting/custom_home_models.dart | 2 + lib/page/setting/setting_screen.dart | 2 +- 13 files changed, 225 insertions(+), 81 deletions(-) create mode 100644 lib/page/chat/component/group_empty.dart diff --git a/lib/helper/ability.dart b/lib/helper/ability.dart index 3e7392c0..07ba3984 100644 --- a/lib/helper/ability.dart +++ b/lib/helper/ability.dart @@ -113,6 +113,10 @@ class Ability { /// 是否支持语音合成功能 bool supportSpeak() { // return setting.stringDefault(settingAPIServerToken, '') != ''; + if (PlatformTool.isWeb()) { + return false; + } + return true; } diff --git a/lib/page/chat/component/group_empty.dart b/lib/page/chat/component/group_empty.dart new file mode 100644 index 00000000..1af44dc8 --- /dev/null +++ b/lib/page/chat/component/group_empty.dart @@ -0,0 +1,107 @@ +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:flutter/material.dart'; + +class GroupEmptyBoard extends StatelessWidget { + const GroupEmptyBoard({super.key}); + + @override + Widget build(BuildContext context) { + final customColors = Theme.of(context).extension()!; + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + const SizedBox(height: 30), + Container( + decoration: BoxDecoration( + color: customColors.backgroundColor?.withAlpha(200), + borderRadius: BorderRadius.circular(10), + ), + padding: + const EdgeInsets.only(top: 20, left: 15, right: 10, bottom: 3), + width: _resolveTipWidth(context), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset('assets/app-256-transparent.png', + width: 20, height: 20), + const SizedBox(width: 5), + const Text( + '小提示', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 20), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildTextLine( + customColors, + "点击 @ 按钮,快速指定应答成员", + Icons.touch_app, + ), + buildTextLine( + customColors, + '未选择成员时,系统将随机指派', + Icons.shuffle, + ), + buildTextLine( + customColors, + '系统会记住上次使用的成员', + Icons.memory, + ), + ], + ), + const SizedBox(height: 20), + ], + ), + ), + ], + ), + ); + } + + Widget buildTextLine(CustomColors customColors, String text, IconData? icon) { + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + icon, + size: 14, + color: customColors.chatExampleItemText?.withAlpha(120), + ), + const SizedBox(width: 5), + Expanded( + child: Text( + text, + maxLines: 1, + style: TextStyle( + color: customColors.weakTextColor, + height: 1.5, + fontSize: 14, + ), + ), + ) + ], + ), + ); + } + + double _resolveTipWidth(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + if (screenWidth < 400) { + return screenWidth / 1.15; + } + + return 400; + } +} diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index a68659cb..6d640988 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -6,9 +6,11 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/image.dart'; import 'package:askaide/lang/lang.dart'; +import 'package:askaide/page/chat/component/group_empty.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/chat/chat_share.dart'; +import 'package:askaide/page/component/chat/empty.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; @@ -333,6 +335,13 @@ class _GroupChatPageState extends State { buildWhen: (prv, cur) => cur is GroupChatMessagesLoaded, builder: (context, state) { if (state is GroupChatMessagesLoaded) { + if (state.messages.isEmpty) { + return const Padding( + padding: EdgeInsets.symmetric(horizontal: 10), + child: GroupEmptyBoard(), + ); + } + final loadedMessages = state.messages.map((e) { var member = e.memberId != null ? group.group.findMember(e.memberId!) : null; diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index c7d632f6..af5d4fc4 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -42,7 +42,7 @@ class _GroupCreatePageState extends State { // 加载模型 APIServer().models().then((value) { setState(() { - models = value; + models = value.where((e) => !e.disabled).toList(); }); }); } diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 4072ec03..7191d69e 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -155,6 +155,14 @@ class _GroupEditPageState extends State { .where((e) => e != null) .map((e) => e!) .toList(); + + final selectedModelIds = + selectedModels.map((e) => e.model.realModelId).toList(); + + models = models + .where((e) => + !e.disabled || selectedModelIds.contains(e.realModelId)) + .toList(); } }, builder: (context, state) { diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index 7ce2838b..b5727bb3 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -238,7 +238,7 @@ class _RoomChatPageState extends State { if (loadedMessages.isEmpty) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric(horizontal: 15), child: EmptyPreview( examples: room.examples ?? [], onSubmit: _handleSubmit, diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 4ab64998..6e01b9e3 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -610,6 +610,7 @@ void openSelectModelDialog( BuildContext context, Function(mm.Model selected) onSelected, { String? initValue, + List? reservedModels, }) { openModalBottomSheet( context, @@ -626,7 +627,11 @@ void openSelectModelDialog( } return ModelItem( - models: snapshot.data!, + models: snapshot.data! + .where((e) => + !e.disabled || + (reservedModels != null && reservedModels.contains(e.id))) + .toList(), onSelected: (selected) { onSelected(selected); context.pop(); diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 3f731007..33043240 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -65,6 +65,7 @@ class _RoomEditPageState extends State { bool showAdvancedOptions = false; mm.Model? _selectedModel; + String? reservedModel; @override void initState() { @@ -116,6 +117,7 @@ class _RoomEditPageState extends State { ModelAggregate.model(state.room.model).then((value) { setState(() { _selectedModel = value; + reservedModel = value.id; }); }); @@ -273,6 +275,9 @@ class _RoomEditPageState extends State { }); }, initValue: _selectedModel?.uid(), + reservedModels: reservedModel != null + ? [reservedModel!] + : [], ); }, value: _selectedModel != null diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index bf9227c0..120b328d 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -66,7 +66,7 @@ class AccountQuotaCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( AppLocale.usage.getString(context), diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index acf2c2cc..a758deb5 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -60,48 +60,50 @@ class _ChatShareScreenState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, actions: [ - TextButton( - onPressed: () async { - final cancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return LoadingIndicator( - message: AppLocale.processingWait.getString(context), - ); - }, - allowClick: false, - duration: const Duration(seconds: 15), - ); + if (!PlatformTool.isWeb()) + TextButton( + onPressed: () async { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 15), + ); - try { - final data = await controller.capture(); - if (data != null) { - final file = await writeTempFile('share-image.png', data); + try { + final data = await controller.capture(); + if (data != null) { + final file = await writeTempFile('share-image.png', data); + cancel(); + // ignore: use_build_context_synchronously + await shareTo( + context, + content: 'images', + images: [ + file.path, + ], + ); + } + } finally { cancel(); - // ignore: use_build_context_synchronously - await shareTo( - context, - content: 'images', - images: [ - file.path, - ], - ); } - } finally { - cancel(); - } - }, - child: Row( - children: [ - Icon(Icons.share, size: 14, color: customColors.weakLinkColor), - const SizedBox(width: 5), - Text( - '分享', - style: TextStyle( - color: customColors.weakLinkColor, fontSize: 14), - ), - ], + }, + child: Row( + children: [ + Icon(Icons.share, + size: 14, color: customColors.weakLinkColor), + const SizedBox(width: 5), + Text( + '分享', + style: TextStyle( + color: customColors.weakLinkColor, fontSize: 14), + ), + ], + ), ), - ), EnhancedPopupMenu( items: [ EnhancedPopupMenuItem( diff --git a/lib/page/component/gallery_item_share.dart b/lib/page/component/gallery_item_share.dart index 959c8647..3bd0596e 100644 --- a/lib/page/component/gallery_item_share.dart +++ b/lib/page/component/gallery_item_share.dart @@ -49,48 +49,50 @@ class _GalleryItemShareScreenState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, actions: [ - TextButton( - onPressed: () async { - final cancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return LoadingIndicator( - message: AppLocale.processingWait.getString(context), - ); - }, - allowClick: false, - duration: const Duration(seconds: 15), - ); + if (!PlatformTool.isWeb()) + TextButton( + onPressed: () async { + final cancel = BotToast.showCustomLoading( + toastBuilder: (cancel) { + return LoadingIndicator( + message: AppLocale.processingWait.getString(context), + ); + }, + allowClick: false, + duration: const Duration(seconds: 15), + ); - try { - final data = await controller.capture(); - if (data != null) { - final file = await writeTempFile('share-image.png', data); + try { + final data = await controller.capture(); + if (data != null) { + final file = await writeTempFile('share-image.png', data); + cancel(); + // ignore: use_build_context_synchronously + await shareTo( + context, + content: 'images', + images: [ + file.path, + ], + ); + } + } finally { cancel(); - // ignore: use_build_context_synchronously - await shareTo( - context, - content: 'images', - images: [ - file.path, - ], - ); } - } finally { - cancel(); - } - }, - child: Row( - children: [ - Icon(Icons.share, size: 14, color: customColors.weakLinkColor), - const SizedBox(width: 5), - Text( - '分享', - style: TextStyle( - color: customColors.weakLinkColor, fontSize: 14), - ), - ], + }, + child: Row( + children: [ + Icon(Icons.share, + size: 14, color: customColors.weakLinkColor), + const SizedBox(width: 5), + Text( + '分享', + style: TextStyle( + color: customColors.weakLinkColor, fontSize: 14), + ), + ], + ), ), - ), EnhancedPopupMenu( items: [ EnhancedPopupMenuItem( diff --git a/lib/page/setting/custom_home_models.dart b/lib/page/setting/custom_home_models.dart index 51e38856..11e45554 100644 --- a/lib/page/setting/custom_home_models.dart +++ b/lib/page/setting/custom_home_models.dart @@ -84,6 +84,7 @@ class _CustomHomeModelsPageState extends State { @override Widget build(BuildContext context) { var customColors = Theme.of(context).extension()!; + var reservedModels = models.map((e) => e.modelId).toList(); return Scaffold( appBar: AppBar( @@ -125,6 +126,7 @@ class _CustomHomeModelsPageState extends State { setState(() {}); }, initValue: models[i].modelId, + reservedModels: reservedModels, ); }, child: Stack( diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index ab2d0c57..afbb3332 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -562,7 +562,7 @@ class _SettingScreenState extends State { ? AppLocale.enable.getString(context) : AppLocale.disable.getString(context), style: TextStyle( - color: customColors.weakTextColor, + color: customColors.weakTextColor?.withAlpha(200), fontSize: 13, ), ), From 36233dd325a6df1a88cee4dc0de6bf7406d936ea Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sat, 28 Oct 2023 01:15:22 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/page/chat/component/group_empty.dart | 3 ++- lib/page/chat/group/chat.dart | 10 +++++++--- lib/page/chat/room_chat.dart | 6 +++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/page/chat/component/group_empty.dart b/lib/page/chat/component/group_empty.dart index 1af44dc8..6be860a5 100644 --- a/lib/page/chat/component/group_empty.dart +++ b/lib/page/chat/component/group_empty.dart @@ -70,7 +70,7 @@ class GroupEmptyBoard extends StatelessWidget { Widget buildTextLine(CustomColors customColors, String text, IconData? icon) { return Padding( - padding: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.only(bottom: 10, left: 15), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -88,6 +88,7 @@ class GroupEmptyBoard extends StatelessWidget { color: customColors.weakTextColor, height: 1.5, fontSize: 14, + overflow: TextOverflow.ellipsis, ), ), ) diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index 6d640988..d8208e4a 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -336,9 +336,13 @@ class _GroupChatPageState extends State { builder: (context, state) { if (state is GroupChatMessagesLoaded) { if (state.messages.isEmpty) { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 10), - child: GroupEmptyBoard(), + return Padding( + padding: EdgeInsets.only( + left: 15, + right: 15, + top: (MediaQuery.of(context).size.height - 300) / 6, + ), + child: const GroupEmptyBoard(), ); } diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index b5727bb3..ffa3dead 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -238,7 +238,11 @@ class _RoomChatPageState extends State { if (loadedMessages.isEmpty) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), + padding: EdgeInsets.only( + left: 15, + right: 15, + top: (MediaQuery.of(context).size.height - 300) / 6, + ), child: EmptyPreview( examples: room.examples ?? [], onSubmit: _handleSubmit, From 6d7a31f2dfbc049aac90aeb4a25aa0a7c2ae9ef5 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sat, 28 Oct 2023 01:42:04 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E5=BE=AE=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/page/chat/group/chat.dart | 10 +++------- lib/page/chat/room_chat.dart | 6 +----- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index d8208e4a..c5ef8509 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -336,13 +336,9 @@ class _GroupChatPageState extends State { builder: (context, state) { if (state is GroupChatMessagesLoaded) { if (state.messages.isEmpty) { - return Padding( - padding: EdgeInsets.only( - left: 15, - right: 15, - top: (MediaQuery.of(context).size.height - 300) / 6, - ), - child: const GroupEmptyBoard(), + return const Padding( + padding: EdgeInsets.only(left: 15, right: 15, top: 10), + child: GroupEmptyBoard(), ); } diff --git a/lib/page/chat/room_chat.dart b/lib/page/chat/room_chat.dart index ffa3dead..2df998ee 100644 --- a/lib/page/chat/room_chat.dart +++ b/lib/page/chat/room_chat.dart @@ -238,11 +238,7 @@ class _RoomChatPageState extends State { if (loadedMessages.isEmpty) { return Padding( - padding: EdgeInsets.only( - left: 15, - right: 15, - top: (MediaQuery.of(context).size.height - 300) / 6, - ), + padding: const EdgeInsets.only(left: 15, right: 15, top: 10), child: EmptyPreview( examples: room.examples ?? [], onSubmit: _handleSubmit, From 4e7f1d63d08b343d925dd61628778593fff440cc Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sat, 28 Oct 2023 17:44:27 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E6=99=BA=E6=85=A7=E6=9E=9C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 17 ++- ...etail_screen.dart => payment_history.dart} | 8 +- lib/page/balance/quota_usage_details.dart | 127 ++++++++++++++++++ lib/page/balance/quota_usage_statistics.dart | 119 ++++++++-------- lib/repo/api_server.dart | 16 +++ lib/repo/model/misc.dart | 26 ++++ 6 files changed, 244 insertions(+), 69 deletions(-) rename lib/page/balance/{quota_detail_screen.dart => payment_history.dart} (96%) create mode 100644 lib/page/balance/quota_usage_details.dart diff --git a/lib/main.dart b/lib/main.dart index fe7079ca..d540b8b6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:askaide/helper/model_resolver.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/data/migrate.dart'; +import 'package:askaide/page/balance/quota_usage_details.dart'; import 'package:askaide/page/setting/account_security.dart'; import 'package:askaide/page/app_scaffold.dart'; import 'package:askaide/page/lab/avatar_selector.dart'; @@ -54,7 +55,7 @@ import 'package:askaide/page/balance/quota_usage_statistics.dart'; import 'package:askaide/page/auth/signin_or_signup.dart'; import 'package:askaide/page/auth/signin_screen.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; -import 'package:askaide/page/balance/quota_detail_screen.dart'; +import 'package:askaide/page/balance/payment_history.dart'; import 'package:askaide/page/setting/retrieve_password_screen.dart'; import 'package:askaide/page/auth/signup_screen.dart'; import 'package:askaide/page/lab/user_center.dart'; @@ -74,6 +75,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:fluwx/fluwx.dart'; import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:askaide/bloc/bloc_manager.dart'; import 'package:askaide/bloc/chat_message_bloc.dart'; @@ -752,7 +754,7 @@ class MyApp extends StatefulWidget { name: 'quota-details', path: '/quota-details', pageBuilder: (context, state) => transitionResolver( - QuotaDetailScreen(setting: settingRepo), + PaymentHistoryScreen(setting: settingRepo), ), ), GoRoute( @@ -762,6 +764,17 @@ class MyApp extends StatefulWidget { QuotaUsageStatisticsScreen(setting: settingRepo), ), ), + GoRoute( + name: 'quota-usage-daily-details', + path: '/quota-usage-daily-details', + pageBuilder: (context, state) => transitionResolver( + QuotaUsageDetailScreen( + setting: settingRepo, + date: state.queryParameters['date'] ?? + DateFormat('yyyy-MM-dd').format(DateTime.now()), + ), + ), + ), GoRoute( name: 'prompt-editor', path: '/prompt-editor', diff --git a/lib/page/balance/quota_detail_screen.dart b/lib/page/balance/payment_history.dart similarity index 96% rename from lib/page/balance/quota_detail_screen.dart rename to lib/page/balance/payment_history.dart index 296e780a..8848a355 100644 --- a/lib/page/balance/quota_detail_screen.dart +++ b/lib/page/balance/payment_history.dart @@ -11,15 +11,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:intl/intl.dart'; -class QuotaDetailScreen extends StatefulWidget { +class PaymentHistoryScreen extends StatefulWidget { final SettingRepository setting; - const QuotaDetailScreen({super.key, required this.setting}); + const PaymentHistoryScreen({super.key, required this.setting}); @override - State createState() => _QuotaDetailScreenState(); + State createState() => _PaymentHistoryScreenState(); } -class _QuotaDetailScreenState extends State { +class _PaymentHistoryScreenState extends State { @override Widget build(BuildContext context) { var customColors = Theme.of(context).extension()!; diff --git a/lib/page/balance/quota_usage_details.dart b/lib/page/balance/quota_usage_details.dart new file mode 100644 index 00000000..3112f2e8 --- /dev/null +++ b/lib/page/balance/quota_usage_details.dart @@ -0,0 +1,127 @@ +import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/loading.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; +import 'package:askaide/page/component/theme/custom_theme.dart'; +import 'package:askaide/repo/api_server.dart'; +import 'package:askaide/repo/model/misc.dart'; +import 'package:askaide/repo/settings_repo.dart'; +import 'package:flutter/material.dart'; + +class QuotaUsageDetailScreen extends StatefulWidget { + final SettingRepository setting; + final String date; + + const QuotaUsageDetailScreen({ + super.key, + required this.setting, + required this.date, + }); + + @override + State createState() => _QuotaUsageDetailScreenState(); +} + +class _QuotaUsageDetailScreenState extends State { + List usages = []; + bool loaded = false; + + @override + void initState() { + APIServer().quotaUsedDetails(date: widget.date).then((value) { + setState(() { + usages = value; + loaded = true; + }); + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + var customColors = Theme.of(context).extension()!; + + return Scaffold( + appBar: AppBar( + toolbarHeight: CustomSize.toolbarHeight, + title: Text( + widget.date, + style: const TextStyle(fontSize: CustomSize.appBarTitleSize), + ), + centerTitle: true, + elevation: 0, + ), + backgroundColor: customColors.backgroundContainerColor, + body: BackgroundContainer( + setting: widget.setting, + enabled: false, + child: Container( + padding: const EdgeInsets.all(16), + child: _buildQuotaUsagePage(context, customColors), + ), + ), + ); + } + + Widget _buildQuotaUsagePage( + BuildContext context, + CustomColors customColors, + ) { + if (!loaded) { + return const Center( + child: LoadingIndicator(), + ); + } + + final usageGt0 = usages.where((e) => e.used > 0).toList(); + if (usageGt0.isEmpty) { + return const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 50, + ), + SizedBox(height: 10), + Text( + '暂无使用记录', + ), + ], + ), + ); + } + + return Column( + children: [ + Expanded( + child: ListView( + shrinkWrap: true, + children: [ + for (var item in usageGt0) + Container( + margin: const EdgeInsets.symmetric(vertical: 6), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: customColors.paymentItemBackgroundColor, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(item.createdAt, + style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(width: 20), + Expanded( + child: Text('使用 ${item.type} 消耗 ${item.used} 个智慧果'), + ), + ], + ), + ) + ], + ), + ), + ], + ); + } +} diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 1355eb80..22b1628a 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -1,5 +1,6 @@ import 'package:askaide/helper/helper.dart'; import 'package:askaide/page/component/background_container.dart'; +import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -7,6 +8,7 @@ import 'package:askaide/repo/api_server.dart'; import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:askaide/repo/model/misc.dart'; +import 'package:go_router/go_router.dart'; class QuotaUsageStatisticsScreen extends StatefulWidget { final SettingRepository setting; @@ -19,6 +21,21 @@ class QuotaUsageStatisticsScreen extends StatefulWidget { class _QuotaUsageStatisticsScreenState extends State { + List usages = []; + bool loaded = false; + + @override + void initState() { + APIServer().quotaUsedStatistics().then((value) { + setState(() { + usages = value; + loaded = true; + }); + }); + + super.initState(); + } + @override Widget build(BuildContext context) { var customColors = Theme.of(context).extension()!; @@ -39,49 +56,17 @@ class _QuotaUsageStatisticsScreenState enabled: false, child: Container( padding: const EdgeInsets.all(16), - child: FutureBuilder( - future: APIServer().quotaUsedStatistics(), - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - Icons.error_outline, - size: 50, - color: Colors.red, - ), - const SizedBox(height: 10), - Text( - resolveError(context, snapshot.error!), - style: const TextStyle(color: Colors.red), - ), - ], - ), - ); - } - - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - return Column( - children: [ - const MessageBox( - message: '使用明细将在次日更新,显示近 30 天的使用量。', - type: MessageBoxType.info, - ), - const SizedBox(height: 10), - Expanded( - child: _buildQuotaUsagePage( - context, snapshot.data!, customColors), - ), - ], - ); - }, + child: Column( + children: [ + const MessageBox( + message: '使用明细将在次日更新,显示近 30 天的使用量。', + type: MessageBoxType.info, + ), + const SizedBox(height: 10), + Expanded( + child: _buildQuotaUsagePage(context, customColors), + ), + ], ), ), ), @@ -90,15 +75,20 @@ class _QuotaUsageStatisticsScreenState Widget _buildQuotaUsagePage( BuildContext context, - List usages, CustomColors customColors, ) { - final usageGt0 = usages.where((e) => e.used > 0).toList(); + if (!loaded) { + return const Center( + child: LoadingIndicator(), + ); + } + + final usageGt0 = usages.where((e) => e.used > 0 || e.used == -1).toList(); if (usageGt0.isEmpty) { - return Center( + return const Center( child: Column( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ Icon( Icons.error_outline, size: 50, @@ -126,23 +116,26 @@ class _QuotaUsageStatisticsScreenState color: customColors.paymentItemBackgroundColor, borderRadius: BorderRadius.circular(10), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(item.date), - Row( - children: [ - if (item.used > 0) const Text('-'), - Text( - '${item.used}', - style: TextStyle( - fontWeight: - item.used > 0 ? FontWeight.bold : null, - ), + child: InkWell( + onTap: () { + context + .push('/quota-usage-daily-details?date=${item.date}'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + item.date, + style: const TextStyle( + fontWeight: FontWeight.bold, ), - ], - ), - ], + ), + if (item.used == -1) + const Text('未出账') + else + Text('${item.used > 0 ? "-" : ""}${item.used}'), + ], + ), ), ) ], diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index ffe91df1..a81080a0 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -1374,6 +1374,22 @@ class APIServer { ); } + /// 获取用户智慧果消耗历史记录详情 + Future> quotaUsedDetails( + {required String date}) async { + return sendGetRequest( + '/v1/users/quota/usage-stat/$date', + (resp) { + var res = []; + for (var item in resp.data['data']) { + res.add(QuotaUsageDetailInDay.fromJson(item)); + } + + return res; + }, + ); + } + Future> creativeGallery({ bool cache = true, int page = 1, diff --git a/lib/repo/model/misc.dart b/lib/repo/model/misc.dart index a7e41c82..7ac5dae9 100644 --- a/lib/repo/model/misc.dart +++ b/lib/repo/model/misc.dart @@ -127,6 +127,32 @@ class QuotaUsageInDay { } } +class QuotaUsageDetailInDay { + int used; + String type; + String createdAt; + + QuotaUsageDetailInDay({ + required this.used, + required this.type, + required this.createdAt, + }); + + toJson() => { + 'used': used, + 'type': type, + 'created_at': createdAt, + }; + + static QuotaUsageDetailInDay fromJson(Map json) { + return QuotaUsageDetailInDay( + used: json['used'], + type: json['type'], + createdAt: json['created_at'], + ); + } +} + class RoomsResponse { List rooms; List? suggests; From d958c66c6a1184a1f69b6db95437248f69a42d02 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sat, 28 Oct 2023 19:01:00 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=E7=BB=98=E7=8E=A9=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E4=B8=8B=E6=9E=B6=E6=9F=90=E4=B8=AA=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/gallery_bloc.dart | 5 +- lib/bloc/gallery_state.dart | 3 +- lib/main.dart | 82 +- lib/page/creative_island/creative_island.dart | 144 --- .../creative_island_create_page.dart | 1008 ----------------- .../creative_island_gallery.dart | 138 --- .../creative_island_history.dart | 337 ------ .../{ => draw/components}/box.dart | 0 .../components}/content_preview.dart | 0 .../creative_island/draw/draw_create.dart | 9 +- .../draw/{draw.dart => draw_list.dart} | 8 +- .../draw_result.dart} | 12 +- .../draw/image_edit_direct.dart | 6 +- .../creative_island/gallery/gallery_item.dart | 40 + lib/page/creative_island/list.dart | 36 - ...land_history_all.dart => my_creation.dart} | 10 +- ...ory_preview.dart => my_creation_item.dart} | 12 +- lib/repo/api/creative.dart | 23 + lib/repo/api_server.dart | 4 +- 19 files changed, 103 insertions(+), 1774 deletions(-) delete mode 100644 lib/page/creative_island/creative_island.dart delete mode 100644 lib/page/creative_island/creative_island_create_page.dart delete mode 100644 lib/page/creative_island/creative_island_gallery.dart delete mode 100644 lib/page/creative_island/creative_island_history.dart rename lib/page/creative_island/{ => draw/components}/box.dart (100%) rename lib/page/creative_island/{ => draw/components}/content_preview.dart (100%) rename lib/page/creative_island/draw/{draw.dart => draw_list.dart} (94%) rename lib/page/creative_island/{creative_island_result.dart => draw/draw_result.dart} (95%) delete mode 100644 lib/page/creative_island/list.dart rename lib/page/creative_island/{creative_island_history_all.dart => my_creation.dart} (98%) rename lib/page/creative_island/{creative_island_history_preview.dart => my_creation_item.dart} (97%) diff --git a/lib/bloc/gallery_bloc.dart b/lib/bloc/gallery_bloc.dart index 2f83a7e3..da0b6c42 100644 --- a/lib/bloc/gallery_bloc.dart +++ b/lib/bloc/gallery_bloc.dart @@ -29,7 +29,10 @@ class GalleryBloc extends Bloc { id: event.id, ); - emit(GalleryItemLoaded(item: res)); + emit(GalleryItemLoaded( + item: res.item, + isInternalUser: res.isInternalUser, + )); }); } } diff --git a/lib/bloc/gallery_state.dart b/lib/bloc/gallery_state.dart index dd36c0e8..a0b587b8 100644 --- a/lib/bloc/gallery_state.dart +++ b/lib/bloc/gallery_state.dart @@ -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}); } diff --git a/lib/main.dart b/lib/main.dart index d540b8b6..6470800f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,12 +28,8 @@ import 'package:askaide/page/chat/home_chat_history.dart'; import 'package:askaide/page/chat/room_create.dart'; import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/transition_resolver.dart'; -import 'package:askaide/page/creative_island/creative_island.dart'; -import 'package:askaide/page/creative_island/creative_island_create_page.dart'; -import 'package:askaide/page/creative_island/creative_island_gallery.dart'; -import 'package:askaide/page/creative_island/creative_island_history.dart'; -import 'package:askaide/page/creative_island/creative_island_history_all.dart'; -import 'package:askaide/page/creative_island/creative_island_history_preview.dart'; +import 'package:askaide/page/creative_island/my_creation.dart'; +import 'package:askaide/page/creative_island/my_creation_item.dart'; import 'package:askaide/page/setting/custom_home_models.dart'; import 'package:askaide/page/balance/free_statistics.dart'; import 'package:askaide/page/chat/group/chat.dart'; @@ -42,7 +38,7 @@ import 'package:askaide/page/chat/group/edit.dart'; import 'package:askaide/page/lab/creative_models.dart'; import 'package:askaide/page/setting/destroy_account.dart'; import 'package:askaide/page/setting/diagnosis.dart'; -import 'package:askaide/page/creative_island/draw/draw.dart'; +import 'package:askaide/page/creative_island/draw/draw_list.dart'; import 'package:askaide/page/creative_island/draw/draw_create.dart'; import 'package:askaide/page/creative_island/draw/image_edit_direct.dart'; import 'package:askaide/page/lab/draw_board.dart'; @@ -548,20 +544,6 @@ class MyApp extends StatefulWidget { ), ), ), - GoRoute( - name: 'creative-island', - path: '/creative-island', - pageBuilder: (context, state) => transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: creativeIslandBloc), - ], - child: CreativeIsland( - setting: settingRepo, - ), - ), - ), - ), GoRoute( name: 'creative-draw', path: '/creative-draw', @@ -570,7 +552,7 @@ class MyApp extends StatefulWidget { providers: [ BlocProvider.value(value: creativeIslandBloc), ], - child: DrawScreen( + child: DrawListScreen( setting: settingRepo, ), ), @@ -644,25 +626,6 @@ class MyApp extends StatefulWidget { ), ), ), - GoRoute( - name: 'creative-island-create', - path: '/creative-island/:id/create', - pageBuilder: (context, state) { - final id = state.pathParameters['id']!; - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: creativeIslandBloc), - ], - child: CreativeIslandCreatePage( - id: id, - repo: creativeIslandRepo, - setting: settingRepo, - ), - ), - ); - }, - ), GoRoute( name: 'creative-island-history-all', path: '/creative-island/history', @@ -672,7 +635,7 @@ class MyApp extends StatefulWidget { providers: [ BlocProvider.value(value: creativeIslandBloc), ], - child: CreativeIslandHistoriesAllScreen( + child: MyCreationScreen( setting: settingRepo, mode: state.queryParameters['mode'] ?? '', ), @@ -680,20 +643,6 @@ class MyApp extends StatefulWidget { ); }, ), - GoRoute( - name: 'creative-island-gallery', - path: '/creative-island/gallery', - pageBuilder: (context, state) { - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: creativeIslandBloc), - ], - child: CreativeIslandGalleryScreen(setting: settingRepo), - ), - ); - }, - ), GoRoute( name: 'creative-island-models', path: '/creative-island/models', @@ -708,25 +657,6 @@ class MyApp extends StatefulWidget { ); }, ), - GoRoute( - name: 'creative-island-history', - path: '/creative-island/:id/history', - pageBuilder: (context, state) { - final id = state.pathParameters['id']!; - return transitionResolver( - MultiBlocProvider( - providers: [ - BlocProvider.value(value: creativeIslandBloc), - ], - child: CreativeIslandHistoryPage( - id: id, - repo: creativeIslandRepo, - setting: settingRepo, - ), - ), - ); - }, - ), GoRoute( name: 'creative-island-history-item', path: '/creative-island/:id/history/:item_id', @@ -740,7 +670,7 @@ class MyApp extends StatefulWidget { providers: [ BlocProvider.value(value: creativeIslandBloc), ], - child: CreativeIslandHistoryPreview( + child: MyCreationItemPage( setting: settingRepo, islandId: id, itemId: itemId!, diff --git a/lib/page/creative_island/creative_island.dart b/lib/page/creative_island/creative_island.dart deleted file mode 100644 index 8cca45cb..00000000 --- a/lib/page/creative_island/creative_island.dart +++ /dev/null @@ -1,144 +0,0 @@ -import 'package:animated_button_bar/animated_button_bar.dart'; -import 'package:animated_text_kit/animated_text_kit.dart'; -import 'package:askaide/bloc/creative_island_bloc.dart'; -import 'package:askaide/helper/ability.dart'; -import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/enhanced_error.dart'; -import 'package:askaide/page/component/sliver_component.dart'; -import 'package:askaide/page/component/weak_text_button.dart'; -import 'package:askaide/page/creative_island/box.dart'; -import 'package:askaide/page/component/theme/custom_size.dart'; -import 'package:askaide/page/component/theme/custom_theme.dart'; -import 'package:askaide/repo/settings_repo.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localization/flutter_localization.dart'; -import 'package:go_router/go_router.dart'; - -enum CreativeIslandMode { - /// 创作岛 - creativeIsland, - - /// 绘图 - imageDraw; - - String getString() { - switch (this) { - case CreativeIslandMode.creativeIsland: - return 'creative-island'; - case CreativeIslandMode.imageDraw: - return 'image-draw'; - } - } -} - -/// 创作岛 -class CreativeIsland extends StatefulWidget { - final SettingRepository setting; - const CreativeIsland({super.key, required this.setting}); - - @override - State createState() => _CreativeIslandState(); -} - -class _CreativeIslandState extends State { - String? selectedCategory; - AnimatedButtonController controller = AnimatedButtonController(); - - @override - void initState() { - if (Ability().supportAPIServer()) { - context.read().add(CreativeIslandListLoadEvent( - mode: CreativeIslandMode.creativeIsland.getString())); - } - super.initState(); - } - - @override - void dispose() { - controller.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final customColors = Theme.of(context).extension()!; - return BackgroundContainer( - setting: widget.setting, - child: Scaffold( - backgroundColor: Colors.transparent, - body: Ability().supportAPIServer() - ? _buildIslandItems(customColors) - : Center( - child: WeakTextButton( - onPressed: () { - context.push('/login'); - }, - title: AppLocale.creativeIslandNeedSignIn.getString(context), - icon: Icons.account_circle, - ), - ), - ), - ); - } - - /// 创作岛列表 - Widget _buildIslandItems( - CustomColors customColors, - ) { - return BlocBuilder( - buildWhen: (previous, current) => current is CreativeIslandListLoaded, - builder: (context, state) { - if (state is CreativeIslandListLoaded) { - if (state.error != null) { - return EnhancedErrorWidget(error: state.error); - } - - return SliverTabComponent( - tabBarTitles: state.categories, - title: AnimatedTextKit( - isRepeatingAnimation: false, - animatedTexts: [ - TypewriterAnimatedText( - AppLocale.creativeIsland.getString(context), - textStyle: - const TextStyle(fontSize: CustomSize.appBarTitleSize), - speed: const Duration(milliseconds: 150), - ), - ], - ), - crossAxisCount: _calCrossAxisCount(context), - itemsBuilder: (context, tabName) { - return state.items - .where((e) => e.categories.contains(tabName)) - .map((e) => CreativeIslandBox(item: e)) - .toList(); - }, - backgroundImageUrl: state.backgroundImage, - childAspectRatio: 1, - actions: [ - IconButton( - onPressed: () { - context.push( - '/creative-island/history?mode=${CreativeIslandMode.creativeIsland.getString()}'); - }, - icon: const Icon(Icons.list), - ), - ], - ); - } - - return Center( - child: CircularProgressIndicator( - color: customColors.chatInputPanelText, - ), - ); - }, - ); - } - - int _calCrossAxisCount(BuildContext context) { - return (MediaQuery.of(context).size.width / 200).round(); - } -} diff --git a/lib/page/creative_island/creative_island_create_page.dart b/lib/page/creative_island/creative_island_create_page.dart deleted file mode 100644 index 12e079b5..00000000 --- a/lib/page/creative_island/creative_island_create_page.dart +++ /dev/null @@ -1,1008 +0,0 @@ -import 'dart:math'; - -import 'package:askaide/bloc/creative_island_bloc.dart'; -import 'package:askaide/helper/constant.dart'; -import 'package:askaide/helper/haptic_feedback.dart'; -import 'package:askaide/helper/helper.dart'; -import 'package:askaide/helper/upload.dart'; -import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/column_block.dart'; -import 'package:askaide/page/component/enhanced_button.dart'; -import 'package:askaide/page/component/enhanced_input.dart'; -import 'package:askaide/page/component/enhanced_textfield.dart'; -import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/component/item_selector_search.dart'; -import 'package:askaide/page/component/loading.dart'; -import 'package:askaide/page/creative_island/content_preview.dart'; -import 'package:askaide/page/creative_island/creative_island_result.dart'; -import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/component/theme/custom_size.dart'; -import 'package:askaide/page/component/theme/custom_theme.dart'; -import 'package:askaide/repo/api/creative.dart'; -import 'package:askaide/repo/api_server.dart'; -import 'package:askaide/repo/creative_island_repo.dart'; -import 'package:askaide/repo/model/misc.dart'; -import 'package:askaide/repo/settings_repo.dart'; -import 'package:bot_toast/bot_toast.dart'; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localization/flutter_localization.dart'; -import 'package:go_router/go_router.dart'; -import 'package:quickalert/models/quickalert_type.dart'; - -class CreativeIslandCreatePage extends StatefulWidget { - final String id; - final CreativeIslandRepository repo; - final SettingRepository setting; - const CreativeIslandCreatePage({ - super.key, - required this.id, - required this.repo, - required this.setting, - }); - - @override - State createState() => - _CreativeIslandCreatePageState(); -} - -class ImageSize { - final String name; - final int width; - final int height; - - const ImageSize(this.name, this.width, this.height); -} - -class StabilityAIImageStyle { - final String name; - final String value; - - const StabilityAIImageStyle(this.name, this.value); -} - -class _CreativeIslandCreatePageState extends State - with SingleTickerProviderStateMixin { - final TextEditingController _contentController = TextEditingController(); - final TextEditingController _negativeTextController = TextEditingController(); - // 字数限制 - final TextEditingController _wordCountController = TextEditingController(); - - var _generationImageCount = 1; - - bool selectDialogOpened = false; - String? selectedImagePath; - // 是否启用 AI 改写 - bool _enableAIRewrite = false; - // 是否显示高级选项 - bool _showAdvancedOptions = false; - - CreativeIslandItemExtSize? _selectedImageSize; - ModelStyle _imageStyle = ModelStyle(id: '', name: ''); - - @override - void initState() { - super.initState(); - - context - .read() - .add(CreativeIslandItemLoadEvent(widget.id)); - } - - @override - void dispose() { - _contentController.dispose(); - _negativeTextController.dispose(); - _wordCountController.dispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final customColors = Theme.of(context).extension()!; - return BlocConsumer( - listener: (context, state) { - if (state is CreativeIslandItemLoaded) { - setState(() { - _enableAIRewrite = state.item.aiRewriteDefaultValue; - if (!state.item.showAdvanceButton) { - _showAdvancedOptions = true; - } - }); - } - }, - listenWhen: (previous, current) => current is CreativeIslandItemLoaded, - buildWhen: (previous, current) => current is CreativeIslandItemLoaded, - builder: (context, state) { - if (state is CreativeIslandItemLoaded) { - return _buildIslandEditArea( - state, - state.item, - context, - customColors, - ); - } - - return Scaffold( - appBar: AppBar( - title: Text( - AppLocale.creativeIsland.getString(context), - style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - ), - centerTitle: true, - ), - body: const Center( - child: Text('Loading ...'), - ), - ); - }, - ); - } - - /// 构建创意岛编辑区域 - Widget _buildIslandEditArea( - CreativeIslandItemLoaded state, - CreativeIslandItem item, - BuildContext context, - CustomColors customColors, - ) { - return Scaffold( - appBar: AppBar( - title: Text( - item.title, - style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - ), - centerTitle: true, - leading: IconButton( - onPressed: () { - context.pop(); - }, - icon: const Icon(Icons.arrow_back_ios), - ), - flexibleSpace: SizedBox( - width: double.infinity, - child: ShaderMask( - shaderCallback: (rect) { - return const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black, Colors.transparent], - ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); - }, - blendMode: BlendMode.dstIn, - child: state.item.bgImage != null - ? Image( - image: CachedNetworkImageProviderEnhanced( - state.item.bgImage!, - ), - fit: BoxFit.cover, - ) - : null, - ), - ), - actions: [ - IconButton( - onPressed: () { - HapticFeedbackHelper.mediumImpact(); - context.push('/creative-island/${widget.id}/history'); - }, - icon: const Icon(Icons.article_outlined), - ), - ], - ), - body: _buildEditPanel(customColors, state, context), - ); - } - - /// 构建编辑面板 - Widget _buildEditPanel( - CustomColors customColors, - CreativeIslandItemLoaded state, - BuildContext context, - ) { - return BackgroundContainer( - setting: widget.setting, - enabled: false, - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 10), - height: double.infinity, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 上传图片(图生图) - if (state.item.modelType == creativeIslandModelTypeImageToImage) - ColumnBlock( - innerPanding: 10, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '原图', - style: TextStyle( - fontSize: 16, - color: customColors.textfieldLabelColor, - ), - ), - const SizedBox(height: 10), - Material( - borderRadius: BorderRadius.circular(8), - child: InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () async { - if (selectDialogOpened) return; - - selectDialogOpened = true; - HapticFeedbackHelper.mediumImpact(); - FilePickerResult? result = await FilePicker - .platform - .pickFiles(type: FileType.image) - .whenComplete( - () => selectDialogOpened = false); - if (result != null && result.files.isNotEmpty) { - setState(() { - selectedImagePath = result.files.first.path!; - }); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Stack( - children: [ - Container( - decoration: selectedImagePath != null && - selectedImagePath != null - ? BoxDecoration( - image: selectedImagePath != - null && - selectedImagePath != '' - ? DecorationImage( - image: resolveImageProvider( - selectedImagePath!), - fit: BoxFit.cover, - ) - : null, - color: customColors - .backgroundContainerColor - ?.withAlpha(100), - borderRadius: - BorderRadius.circular(8), - ) - : null, - child: const SizedBox( - width: double.infinity, - height: 200, - ), - ), - selectedImagePath == null || - selectedImagePath == '' - ? SizedBox( - height: 200, - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - Icon( - Icons.camera_alt, - size: 30, - color: customColors - .chatInputPanelText, - ), - const SizedBox(width: 10), - Text( - AppLocale.selectImage - .getString(context), - style: TextStyle( - fontSize: 14, - fontWeight: - FontWeight.w500, - color: customColors - .chatInputPanelText - ?.withOpacity(0.8), - ), - ), - ], - ), - ) - : Positioned( - bottom: 0, - left: 0, - right: 0, - child: Container( - color: const Color.fromARGB( - 80, 255, 255, 255), - height: 50, - child: Row( - mainAxisAlignment: - MainAxisAlignment.center, - children: const [ - Icon( - Icons.camera_alt, - size: 30, - color: Color.fromARGB( - 147, 255, 255, 255), - ), - SizedBox(width: 10), - Text( - '点击此处更换图片', - style: TextStyle( - fontSize: 14, - fontWeight: - FontWeight.w500, - color: Color.fromARGB( - 147, 255, 255, 255), - ), - ), - ], - ), - ), - ) - ], - ), - )), - ), - ), - ], - ), - ], - ), - - if (!state.item.noPrompt) - ColumnBlock( - innerPanding: 10, - children: [ - // 生成内容 - EnhancedTextField( - labelPosition: LabelPosition.top, - labelText: state.item.promptInputTitle ?? - (state.item.modelType == creativeIslandModelTypeText - ? AppLocale.writeYourIdeas.getString(context) - : AppLocale.describeYourImages - .getString(context)), - customColors: customColors, - controller: _contentController, - textAlignVertical: TextAlignVertical.top, - hintText: state.item.hint ?? - AppLocale.required.getString(context), - maxLines: 10, - minLines: 5, - maxLength: (state.item.wordCount ?? 0) > 0 - ? state.item.wordCount - : 1000, - showCounter: false, - bottomButton: state.item.modelType == - creativeIslandModelTypeImage - ? Row( - children: [ - Icon( - Icons.shuffle, - size: 13, - color: customColors.linkColor?.withAlpha(150), - ), - const SizedBox(width: 5), - Text( - AppLocale.random.getString(context), - style: TextStyle( - color: - customColors.linkColor?.withAlpha(150), - fontSize: 13, - ), - ), - ], - ) - : null, - bottomButtonOnPressed: () async { - final examples = await APIServer() - .exampleByTag(state.item.modelType); - if (examples.isEmpty) { - return; - } - - // 随机选取一个例子 - final example = - examples[Random().nextInt(examples.length)]; - _contentController.text = example.text; - }, - ), - // 文本类生成选项 - if (state.item.modelType == creativeIslandModelTypeText) - _buildTextGenerationToolbar( - state.item, - customColors, - ), - ], - ), - - if (_showAdvancedOptions) - ColumnBlock( - children: [ - // 排除关键词 - if (state.item.isShowNegativeText) - EnhancedTextField( - labelPosition: LabelPosition.top, - labelText: AppLocale.excludeContents.getString(context), - customColors: customColors, - controller: _negativeTextController, - textAlignVertical: TextAlignVertical.top, - hintText: 'text, blurry, low quality', - maxLength: 500, - showCounter: false, - ), - - if (state.item.isShowAIRewrite) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('AI 优化'), - Switch( - inactiveThumbColor: Colors.white, - inactiveTrackColor: Colors.grey.withOpacity(0.5), - activeColor: customColors.linkColor, - value: _enableAIRewrite, - onChanged: (value) { - setState(() { - _enableAIRewrite = value; - }); - }, - ), - ], - ), - ], - ), - - // 图片风格 - if (_showAdvancedOptions && - (state.item.modelType == - creativeIslandModelTypeImageToImage || - state.item.modelType == creativeIslandModelTypeImage) && - state.item.showImageStyleSelector) - ColumnBlock( - children: [ - _buildImageStyleField( - context, customColors, state.item.vendor), - ], - ), - if (_showAdvancedOptions && - state.item.modelType == creativeIslandModelTypeImage) - ColumnBlock( - children: _buildImageGenerationToolbar( - customColors, - state.item, - ), - ), - - // 生成按钮 - const SizedBox(height: 20), - Row( - children: [ - if (state.item.showAdvanceButton) - EnhancedButton( - title: '高级选项', - width: 100, - backgroundColor: Colors.transparent, - color: customColors.weakLinkColor, - fontSize: 15, - icon: Icon( - _showAdvancedOptions - ? Icons.unfold_less - : Icons.unfold_more, - color: customColors.weakLinkColor, - size: 15, - ), - onPressed: () { - setState(() { - _showAdvancedOptions = !_showAdvancedOptions; - }); - }, - ), - if (state.item.showAdvanceButton) const SizedBox(width: 10), - Expanded( - flex: 1, - child: EnhancedButton( - title: state.item.submitBtnText ?? - AppLocale.generate.getString(context), - onPressed: () { - onGenerate( - context, - state.item.modelType, - state.item.vendor, - state.item.waitSeconds, - state.item.noPrompt, - customColors, - ); - }, - ), - ), - ], - ), - const SizedBox(height: 20), - ], - ), - ), - ), - ); - } - - Widget _buildImageStyleItemPreview(ModelStyle style, {double? size}) { - return Container( - width: size, - height: size, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - image: style.preview != null - ? DecorationImage( - image: CachedNetworkImageProviderEnhanced(style.preview!), - fit: BoxFit.cover, - ) - : null, - ), - child: style.preview == null - ? const Center( - child: Icon( - Icons.interests, - color: Colors.grey, - size: 40, - ), - ) - : null); - } - - Widget _buildImageStyleField( - BuildContext context, - CustomColors customColors, - String vendor, - ) { - return EnhancedInput( - title: Text( - AppLocale.style.getString(context), - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Text(_imageStyle.name == '' - ? AppLocale.auto.getString(context) - : _imageStyle.name), - const SizedBox(width: 10), - _buildImageStyleItemPreview(_imageStyle, size: 50), - ], - ), - onPressed: () { - openModalBottomSheet( - context, - (context) { - return FutureBuilder( - future: APIServer().modelStyles(vendor), - builder: (context, snapshot) { - if (snapshot.hasError) { - showErrorMessage(resolveError(context, snapshot.error!)); - return Text( - resolveError(context, snapshot.error!), - style: const TextStyle(color: Colors.red), - ); - } - - if (!snapshot.hasData) { - return const SizedBox.shrink(); - } - - var data = snapshot.data ?? []; - data.insert( - 0, - ModelStyle( - id: '', - name: AppLocale.auto.getString(context), - ), - ); - - return GridView.count( - crossAxisCount: 3, - crossAxisSpacing: 20, - mainAxisSpacing: 20, - padding: const EdgeInsets.only(top: 20), - children: [ - for (var item in data) - InkWell( - onTap: () { - setState(() { - _imageStyle = item; - }); - - Navigator.pop(context); - }, - child: Column( - children: [ - Expanded( - child: AspectRatio( - aspectRatio: 1, - child: _buildImageStyleItemPreview(item), - ), - ), - const SizedBox(height: 10), - Text( - item.name, - style: const TextStyle(fontSize: 12), - ), - ], - ), - ), - ], - ); - }, - ); - }, - heightFactor: 0.8, - ); - }, - ); - } - - /// 构建文本生成工具栏 - List _buildImageGenerationToolbar( - CustomColors customColors, - CreativeIslandItem item, - ) { - return [ - // 图片数量 - if (item.vendor == modelTypeStabilityAI || item.vendor == modelTypeLeapAI) - EnhancedInput( - title: Text( - AppLocale.imageCount.getString(context), - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: Text(_generationImageCount.toString()), - onPressed: () { - openListSelectDialog( - context, - [ - SelectorItem(const Text('1', textAlign: TextAlign.center), 1), - SelectorItem(const Text('2', textAlign: TextAlign.center), 2), - SelectorItem(const Text('3', textAlign: TextAlign.center), 3), - SelectorItem(const Text('4', textAlign: TextAlign.center), 4), - ], - (value) { - setState(() { - _generationImageCount = value.value; - }); - return true; - }, - // title: AppLocale.imageCount.getString(context), - heightFactor: 0.4, - value: _generationImageCount, - ); - }, - ), - - //图片尺寸 - if (item.imageAllowSizes.isNotEmpty) - EnhancedInput( - title: Text( - AppLocale.imageSize.getString(context), - style: TextStyle( - color: customColors.textfieldLabelColor, - fontSize: 16, - ), - ), - value: _selectedImageSize == null - ? const Text('自动') - : Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - _buildSizeImage(_selectedImageSize!, customColors), - ], - ), - onPressed: () { - openListSelectDialog( - context, - item.imageAllowSizes - .map( - (e) => SelectorItem( - _buildSizeImage(e, customColors), - e.aspectRatio, - ), - ) - .toList(), - (value) { - setState(() { - _selectedImageSize = item.imageAllowSizes - .firstWhere((e) => e.aspectRatio == value.value); - }); - return true; - }, - // title: AppLocale.imageSize.getString(context), - value: _selectedImageSize?.aspectRatio, - heightFactor: 0.25, - horizontal: true, - horizontalCount: 4, - ); - }, - ), - ]; - } - - Widget _buildSizeImage( - CreativeIslandItemExtSize e, - CustomColors customColors, - ) { - final width = e.width > e.height ? 40 : 40 * e.width / e.height; - final height = e.width > e.height ? 40 * e.height / e.width : 40; - return Container( - width: width.toDouble(), - height: height.toDouble(), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(2), - color: customColors.backgroundContainerColor, - ), - alignment: Alignment.center, - child: Text( - e.aspectRatio, - style: const TextStyle(fontSize: 12, color: Colors.white), - textAlign: TextAlign.center, - ), - ); - } - - /// 构建文本生成工具栏 - Widget _buildTextGenerationToolbar( - CreativeIslandItem item, - CustomColors customColors, - ) { - return EnhancedTextField( - labelText: AppLocale.wordCount.getString(context), - labelPosition: LabelPosition.left, - customColors: customColors, - controller: _wordCountController, - hintText: '≤ 1000', - showCounter: false, - keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], - textDirection: TextDirection.rtl, - fieldWidth: 50, - ); - } - - /// 是否停止周期性查询任务执行状态 - var stopPeriodQuery = false; - - /// 生成事件处理 - void onGenerate( - BuildContext context, - String modelType, - String modelCategory, - int waitSeconds, - bool noPrompt, - CustomColors customColors, - ) async { - FocusScope.of(context).requestFocus(FocusNode()); - HapticFeedbackHelper.mediumImpact(); - - final content = _contentController.text.trim(); - if (!noPrompt && content.isEmpty) { - showErrorMessage(AppLocale.contentIsRequired.getString(context)); - return; - } - - var params = {}; - - if (modelType == creativeIslandModelTypeText) { - final wordCount = int.parse( - _wordCountController.text == '' ? '500' : _wordCountController.text); - if (wordCount < 0 || wordCount > 1000) { - showErrorMessage(AppLocale.wordCountInvalid.getString(context)); - return; - } - - params = { - "word_count": wordCount, - "prompt": content, - }; - } else if (modelType == creativeIslandModelTypeImage) { - params = { - "prompt": content, - "width": _selectedImageSize?.width, - "height": _selectedImageSize?.height, - "image_count": _generationImageCount, - "negative_prompt": _negativeTextController.text, - 'style_preset': _imageStyle.id, - 'ai_rewrite': _enableAIRewrite, - }; - } - // 图生图,先上传图片 - else if (modelType == creativeIslandModelTypeImageToImage) { - if (selectedImagePath == null || selectedImagePath == '') { - showErrorMessage('请选择图片'); - return; - } - - params = { - "prompt": content, - "negative_prompt": _negativeTextController.text, - 'style_preset': _imageStyle.id, - "width": _selectedImageSize?.width, - "height": _selectedImageSize?.height, - 'ai_rewrite': _enableAIRewrite, - 'image': - 'https://${selectedImagePath ?? 'demo'}', // 仅用于测试消耗量,正式上传后会被替换为 URL - }; - } - - final cancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return const LoadingIndicator( - message: '思考中,请稍候...', - ); - }, - allowClick: false, - duration: const Duration(seconds: 15), - ); - - request() async { - try { - cancel(); - - if (modelType == creativeIslandModelTypeImageToImage) { - final cancel = BotToast.showCustomLoading( - toastBuilder: (cancel) { - return const LoadingIndicator( - message: '正在上传图片,请稍后...', - ); - }, - allowClick: false, - ); - - final uploadRes = await ImageUploader(widget.setting) - .upload(selectedImagePath!) - .whenComplete(() => cancel()); - params['image'] = uploadRes.url; - } - - final taskId = await APIServer().creativeIslandCompletionsAsync( - widget.id, - params, - ); - - stopPeriodQuery = false; - - // ignore: use_build_context_synchronously - Navigator.push( - context, - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => CreativeIslandResultDialog( - future: _generateResult( - modelType, - taskId, - waitSeconds > 30 ? 5 : 3, - params: params, - ), - waitDuration: waitSeconds, - ), - ), - ).whenComplete(() { - stopPeriodQuery = true; - context.read().add( - CreativeIslandHistoriesLoadEvent(widget.id, forceRefresh: true)); - }); - } catch (e) { - stopPeriodQuery = true; - cancel(); - // ignore: use_build_context_synchronously - showErrorMessage(resolveError(context, e)); - } - } - - try { - final res = await APIServer() - .creativeIslandCompletionsEvaluate(widget.id, params); - if (!res.enough) { - if (context.mounted) { - showBeautyDialog( - context, - type: QuickAlertType.warning, - text: AppLocale.quotaExceeded.getString(context), - confirmBtnText: '立即购买', - showCancelBtn: true, - onConfirmBtnTap: () { - context.pop(); - context.push('/payment'); - }, - ); - } - return; - } - if (res.cost > 0) { - cancel(); - // ignore: use_build_context_synchronously - openConfirmDialog( - context, - '【测试专用】\n本次请求预计消耗 ${res.cost} 个智慧果,是否继续操作?', - () => request(), - ); - } else { - request(); - } - } catch (e) { - cancel(); - showErrorMessageEnhanced(context, e); - } - } - - Future _generateResult( - String modelType, - String taskId, - int delaySeconds, { - Map? params, - }) async { - return await Future.delayed(Duration(seconds: delaySeconds), () async { - return await _queryCompletionTaskStatus( - taskId, - modelType, - 0, - delaySeconds, - params: params, - ); - }); - } - - Future _queryCompletionTaskStatus( - String taskId, - String modelType, - int retryTimes, - int delaySeconds, { - Map? params, - }) async { - if (retryTimes > 60) { - return Future.error(AppLocale.generateTimeout.getString(context)); - } - - final resp = await APIServer().asyncTaskStatus(taskId); - - if (resp.status == 'success') { - if (modelType == creativeIslandModelTypeImage || - modelType == creativeIslandModelTypeImageToImage) { - return IslandResult( - result: resp.resources ?? const [], - params: params, - ); - } - - return IslandResult( - result: [resp.resources!.join("\n\n")], - params: params, - ); - } else if (resp.status == 'failed') { - return Future.error(resp.errors!.join(";")); - } else { - if (stopPeriodQuery) { - // ignore: use_build_context_synchronously - return Future.error(AppLocale.generateTimeout.getString(context)); - } - - return await Future.delayed(Duration(seconds: delaySeconds), () async { - return await _queryCompletionTaskStatus( - taskId, - modelType, - retryTimes + 1, - delaySeconds, - params: params, - ); - }); - } - } -} diff --git a/lib/page/creative_island/creative_island_gallery.dart b/lib/page/creative_island/creative_island_gallery.dart deleted file mode 100644 index 068b2137..00000000 --- a/lib/page/creative_island/creative_island_gallery.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'package:askaide/bloc/creative_island_bloc.dart'; -import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/component/image_preview.dart'; -import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/component/theme/custom_size.dart'; -import 'package:askaide/page/component/theme/custom_theme.dart'; -import 'package:askaide/repo/settings_repo.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -import 'package:sizer/sizer.dart'; - -class CreativeIslandGalleryScreen extends StatefulWidget { - final SettingRepository setting; - - const CreativeIslandGalleryScreen({super.key, required this.setting}); - - @override - State createState() => - _CreativeIslandGalleryScreenState(); -} - -class _CreativeIslandGalleryScreenState - extends State { - @override - void initState() { - context - .read() - .add(CreativeIslandGalleryLoadEvent(mode: "all")); - super.initState(); - } - - @override - Widget build(BuildContext context) { - final customColors = Theme.of(context).extension()!; - - return Scaffold( - appBar: AppBar( - title: const Text( - '创作岛 Gallery', - style: TextStyle(fontSize: CustomSize.appBarTitleSize), - ), - centerTitle: true, - ), - backgroundColor: customColors.chatInputPanelBackground, - body: BackgroundContainer( - setting: widget.setting, - enabled: false, - child: RefreshIndicator( - color: customColors.linkColor, - onRefresh: () async { - context - .read() - .add(CreativeIslandGalleryLoadEvent( - forceRefresh: true, - mode: "all", - )); - }, - child: BlocConsumer( - listenWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, - buildWhen: (previous, current) => - current is CreativeIslandGalleryLoaded, - listener: (context, state) { - if (state is CreativeIslandHistoriesAllLoaded) { - if (state.error != null) { - showErrorMessageEnhanced(context, state.error); - } - } - }, - builder: (context, state) { - if (state is CreativeIslandGalleryLoaded) { - return GridView.count( - padding: const EdgeInsets.all(10), - crossAxisCount: _calCrossAxisCount(), - crossAxisSpacing: 10, - mainAxisSpacing: 10, - children: state.items.where((e) => e.images.isNotEmpty).map( - (e) { - if (e.userId != null && e.userId! > 0) { - return GestureDetector( - onTap: () { - context.push( - '/creative-island/${e.islandId}/history/${e.id}'); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: Colors.amber, - width: 2, - ), - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: CachedNetworkImageEnhanced( - imageUrl: e.firstImagePreview, - fit: BoxFit.cover, - ), - ), - ), - ); - } - - return ClipRRect( - borderRadius: BorderRadius.circular(10), - child: NetworkImagePreviewer( - url: e.firstImagePreview, - hidePreviewButton: true, - ), - ); - }, - ).toList(), - ); - } - return const Center(child: CircularProgressIndicator()); - }, - ), - ), - ), - ); - } - - int _calCrossAxisCount() { - if (SizerUtil.deviceType == DeviceType.tablet) { - if (SizerUtil.orientation == Orientation.landscape) { - return 6; - } - return 4; - } - - if (SizerUtil.orientation == Orientation.landscape) { - return 6; - } - return 3; - } -} diff --git a/lib/page/creative_island/creative_island_history.dart b/lib/page/creative_island/creative_island_history.dart deleted file mode 100644 index 209c1a34..00000000 --- a/lib/page/creative_island/creative_island_history.dart +++ /dev/null @@ -1,337 +0,0 @@ -import 'package:askaide/bloc/creative_island_bloc.dart'; -import 'package:askaide/helper/constant.dart'; -import 'package:askaide/helper/helper.dart'; -import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/component/background_container.dart'; -import 'package:askaide/page/component/image.dart'; -import 'package:askaide/page/creative_island/creative_island.dart'; -import 'package:askaide/page/component/dialog.dart'; -import 'package:askaide/page/component/theme/custom_size.dart'; -import 'package:askaide/page/component/theme/custom_theme.dart'; -import 'package:askaide/repo/api/creative.dart'; -import 'package:askaide/repo/creative_island_repo.dart'; -import 'package:askaide/repo/settings_repo.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_localization/flutter_localization.dart'; -import 'package:flutter_slidable/flutter_slidable.dart'; -import 'package:go_router/go_router.dart'; - -class CreativeIslandHistoryPage extends StatefulWidget { - final String id; - final CreativeIslandRepository repo; - final SettingRepository setting; - const CreativeIslandHistoryPage({ - super.key, - required this.id, - required this.repo, - required this.setting, - }); - - @override - State createState() => - _CreativeIslandHistoryPageState(); -} - -class _CreativeIslandHistoryPageState extends State { - @override - void initState() { - super.initState(); - context - .read() - .add(CreativeIslandHistoriesLoadEvent(widget.id)); - } - - @override - Widget build(BuildContext context) { - final customColors = Theme.of(context).extension()!; - - return BlocConsumer( - listener: (context, state) { - if (state is CreativeIslandHistoriesLoaded) { - if (state.error != null) { - showErrorMessage(state.error); - } - } - }, - listenWhen: (previous, current) { - return current is CreativeIslandHistoriesLoaded; - }, - buildWhen: (previous, current) { - return current is CreativeIslandHistoriesLoaded; - }, - builder: (context, state) { - if (state is CreativeIslandHistoriesLoaded) { - return Scaffold( - appBar: _buildAppBar(context, state, customColors), - // backgroundColor: customColors.chatInputPanelBackground, - backgroundColor: customColors.backgroundContainerColor, - body: BackgroundContainer( - setting: widget.setting, - enabled: false, - child: RefreshIndicator( - color: customColors.linkColor, - onRefresh: () async { - context - .read() - .add(CreativeIslandHistoriesLoadEvent( - widget.id, - forceRefresh: true, - )); - }, - child: state.histories.isNotEmpty - ? _buildHistoryItems(state, customColors) - : Center( - child: Text(AppLocale.noRecords.getString(context))), - ), - ), - ); - } - - return Scaffold( - appBar: _buildAppBar(context, null, customColors), - backgroundColor: customColors.chatInputPanelBackground, - body: const Center(child: CircularProgressIndicator()), - ); - }, - ); - } - - /// 构建历史项目列表 - Widget _buildHistoryItems( - CreativeIslandHistoriesLoaded state, - CustomColors customColors, - ) { - return ListView.builder( - itemCount: state.histories.length, - itemBuilder: (context, index) { - return Container( - padding: EdgeInsets.only(top: index == 0 ? 15 : 10), - child: Container( - margin: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 0, - ), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - ), - child: Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - children: [ - const SizedBox(width: 10), - SlidableAction( - label: AppLocale.delete.getString(context), - borderRadius: const BorderRadius.all(Radius.circular(10)), - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (_) { - openConfirmDialog( - context, AppLocale.confirmDelete.getString(context), - () { - context - .read() - .add(CreativeIslandDeleteEvent( - widget.id, - state.histories[index].id, - mode: (state.island.modelType == - creativeIslandModelTypeImage || - state.island.modelType == - creativeIslandModelTypeImageToImage) - ? CreativeIslandMode.imageDraw.getString() - : CreativeIslandMode.creativeIsland - .getString(), - )); - }); - }, - ), - ], - ), - child: Material( - // color: customColors.chatExampleItemBackground, - borderRadius: const BorderRadius.all( - Radius.circular(10), - ), - // color: Colors.transparent, - child: InkWell( - borderRadius: BorderRadius.circular(10), - onTap: () { - _openHistoryItemDialog(context, state, index, customColors); - }, - child: ListTile( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 15, - vertical: 8, - ), - leading: _buildAnswerImagePreview( - context, state.histories[index]), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (state.histories[index].isTextType || - !state.histories[index].isSuccessful) - _buildPrefixIcon(state.histories[index]), - if (state.histories[index].isTextType || - !state.histories[index].isSuccessful) - const SizedBox(width: 10), - Expanded( - child: Text( - state.histories[index].prompt! - .replaceAll('\n', ' '), - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle(fontSize: 15), - ), - ), - Text( - humanTime(state.histories[index].createdAt), - style: Theme.of(context).textTheme.bodySmall, - ), - ], - ), - ], - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 8), - _buildAnswerTextPreview( - context, state.histories[index]), - ], - ), - ), - ), - ), - ), - ), - ); - }, - ); - } - - Widget? _buildAnswerImagePreview( - BuildContext context, - CreativeItemInServer item, - ) { - if (item.isImageType && item.images.isNotEmpty) { - return SizedBox( - height: 50, - width: 50, - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: CachedNetworkImageEnhanced( - imageUrl: item.firstImagePreview, - fit: BoxFit.cover, - ), - ), - ); - } - - return null; - } - - Widget _buildAnswerTextPreview( - BuildContext context, - CreativeItemInServer item, - ) { - if (item.isFailed) { - return Text( - '创作失败', - style: Theme.of(context).textTheme.bodySmall, - ); - } - - if (item.isProcessing) { - return Text( - '创作中', - style: Theme.of(context).textTheme.bodySmall, - ); - } - - if (item.isImageType && item.images.isNotEmpty) { - return const SizedBox(); - } - - return Text( - (item.answer ?? '').replaceAll('\n', ' '), - style: Theme.of(context).textTheme.bodySmall, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ); - } - - /// 打开历史项目详情对话框 - _openHistoryItemDialog( - BuildContext context, - CreativeIslandHistoriesLoaded state, - int index, - CustomColors customColors, - ) { - context.push( - '/creative-island/${widget.id}/history/${state.histories[index].id}'); - } - - AppBar _buildAppBar( - BuildContext context, - CreativeIslandHistoriesLoaded? state, - CustomColors customColors, - ) { - return AppBar( - title: Text( - AppLocale.histories.getString(context), - style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - ), - centerTitle: true, - flexibleSpace: state != null - ? SizedBox( - width: double.infinity, - child: ShaderMask( - shaderCallback: (rect) { - return const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [Colors.black, Colors.transparent], - ).createShader(Rect.fromLTRB(0, 0, rect.width, rect.height)); - }, - blendMode: BlendMode.dstIn, - child: Image( - image: CachedNetworkImageProviderEnhanced( - state.island.bgImage!, - ), - fit: BoxFit.cover, - ), - ), - ) - : null, - ); - } - - Widget _buildPrefixIcon(CreativeItemInServer his) { - if (his.isFailed) { - return const Icon( - Icons.error_outline, - size: 18, - color: Colors.red, - ); - } - - if (his.isSuccessful) { - return const Icon(Icons.tag, size: 18, color: Colors.green); - } - - if (his.isProcessing) { - return const Icon( - Icons.hourglass_top, - size: 18, - color: Colors.blue, - ); - } - - return const SizedBox(); - } -} diff --git a/lib/page/creative_island/box.dart b/lib/page/creative_island/draw/components/box.dart similarity index 100% rename from lib/page/creative_island/box.dart rename to lib/page/creative_island/draw/components/box.dart diff --git a/lib/page/creative_island/content_preview.dart b/lib/page/creative_island/draw/components/content_preview.dart similarity index 100% rename from lib/page/creative_island/content_preview.dart rename to lib/page/creative_island/draw/components/content_preview.dart diff --git a/lib/page/creative_island/draw/draw_create.dart b/lib/page/creative_island/draw/draw_create.dart index f3e2eb98..384e6fbb 100644 --- a/lib/page/creative_island/draw/draw_create.dart +++ b/lib/page/creative_island/draw/draw_create.dart @@ -12,8 +12,8 @@ import 'package:askaide/page/component/enhanced_textfield.dart'; import 'package:askaide/page/component/item_selector_search.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/prompt_tags_selector.dart'; -import 'package:askaide/page/creative_island/content_preview.dart'; -import 'package:askaide/page/creative_island/creative_island_result.dart'; +import 'package:askaide/page/creative_island/draw/components/content_preview.dart'; +import 'package:askaide/page/creative_island/draw/draw_result.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/creative_island/draw/components/image_selector.dart'; import 'package:askaide/page/creative_island/draw/components/image_size.dart'; @@ -93,7 +93,8 @@ class _DrawCreateScreenState extends State { if (widget.galleryCopyId != null && widget.galleryCopyId! > 0) { APIServer() .creativeGalleryItem(id: widget.galleryCopyId!) - .then((gallery) { + .then((response) { + final gallery = response.item; if (gallery.prompt != null && gallery.prompt!.isNotEmpty) { promptController.text = gallery.prompt!; } @@ -857,7 +858,7 @@ class _DrawCreateScreenState extends State { context, MaterialPageRoute( fullscreenDialog: true, - builder: (context) => CreativeIslandResultDialog( + builder: (context) => DrawResultPage( future: Future.delayed(const Duration(seconds: 10), () async { return await queryCompletionTaskStatus( taskId: taskId, diff --git a/lib/page/creative_island/draw/draw.dart b/lib/page/creative_island/draw/draw_list.dart similarity index 94% rename from lib/page/creative_island/draw/draw.dart rename to lib/page/creative_island/draw/draw_list.dart index b7d630ae..21c120d7 100644 --- a/lib/page/creative_island/draw/draw.dart +++ b/lib/page/creative_island/draw/draw_list.dart @@ -13,15 +13,15 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; -class DrawScreen extends StatefulWidget { +class DrawListScreen extends StatefulWidget { final SettingRepository setting; - const DrawScreen({super.key, required this.setting}); + const DrawListScreen({super.key, required this.setting}); @override - State createState() => _DrawScreenState(); + State createState() => _DrawListScreenState(); } -class _DrawScreenState extends State { +class _DrawListScreenState extends State { @override void initState() { if (Ability().supportAPIServer()) { diff --git a/lib/page/creative_island/creative_island_result.dart b/lib/page/creative_island/draw/draw_result.dart similarity index 95% rename from lib/page/creative_island/creative_island_result.dart rename to lib/page/creative_island/draw/draw_result.dart index 7dae1d69..8e415bf0 100644 --- a/lib/page/creative_island/creative_island_result.dart +++ b/lib/page/creative_island/draw/draw_result.dart @@ -1,7 +1,7 @@ import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/lang/lang.dart'; -import 'package:askaide/page/creative_island/content_preview.dart'; +import 'package:askaide/page/creative_island/draw/components/content_preview.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -9,25 +9,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:circular_countdown_timer/circular_countdown_timer.dart'; -class CreativeIslandResultDialog extends StatefulWidget { +class DrawResultPage extends StatefulWidget { final Future future; final int waitDuration; - const CreativeIslandResultDialog({ + const DrawResultPage({ super.key, required this.future, this.waitDuration = 30, }); @override - State createState() => - _CreativeIslandResultDialogState(); + State createState() => _DrawResultPageState(); } const defaultCounterRestartValue = 15; -class _CreativeIslandResultDialogState - extends State { +class _DrawResultPageState extends State { var loading = true; var restartCounterValue = defaultCounterRestartValue; diff --git a/lib/page/creative_island/draw/image_edit_direct.dart b/lib/page/creative_island/draw/image_edit_direct.dart index 097fa737..f0609225 100644 --- a/lib/page/creative_island/draw/image_edit_direct.dart +++ b/lib/page/creative_island/draw/image_edit_direct.dart @@ -6,8 +6,8 @@ import 'package:askaide/page/component/column_block.dart'; import 'package:askaide/page/component/enhanced_button.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; -import 'package:askaide/page/creative_island/content_preview.dart'; -import 'package:askaide/page/creative_island/creative_island_result.dart'; +import 'package:askaide/page/creative_island/draw/components/content_preview.dart'; +import 'package:askaide/page/creative_island/draw/draw_result.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/creative_island/draw/components/image_selector.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; @@ -193,7 +193,7 @@ class _ImageEditDirectScreenState extends State { context, MaterialPageRoute( fullscreenDialog: true, - builder: (context) => CreativeIslandResultDialog( + builder: (context) => DrawResultPage( future: Future.delayed(const Duration(seconds: 10), () async { return await queryCompletionTaskStatus( taskId: taskId, diff --git a/lib/page/creative_island/gallery/gallery_item.dart b/lib/page/creative_island/gallery/gallery_item.dart index addc76ab..f2f787cc 100644 --- a/lib/page/creative_island/gallery/gallery_item.dart +++ b/lib/page/creative_island/gallery/gallery_item.dart @@ -4,6 +4,7 @@ import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; import 'package:askaide/helper/image.dart'; +import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/attached_button_panel.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; @@ -20,6 +21,7 @@ import 'package:bot_toast/bot_toast.dart'; import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; class GalleryItemScreen extends StatefulWidget { @@ -54,6 +56,44 @@ class _GalleryItemScreenState extends State { ), backgroundColor: customColors.backgroundContainerColor?.withAlpha(200), toolbarHeight: CustomSize.toolbarHeight, + actions: [ + BlocBuilder( + buildWhen: (previous, current) => current is GalleryItemLoaded, + builder: (context, state) { + if (state is GalleryItemLoaded && + state.isInternalUser && + state.item.status == 1) { + return TextButton( + onPressed: () { + openConfirmDialog( + context, + '确认取消?', + () => APIServer() + .cancelShareCreativeHistoryToGallery( + historyId: state.item.creativeHistoryId!) + .then((value) { + showSuccessMessage( + AppLocale.operateSuccess.getString(context)); + + context.read().add(GalleryItemLoadEvent( + id: widget.galleryId, forceRefresh: true)); + }), + ); + }, + child: Text( + '取消共享', + style: TextStyle( + color: customColors.weakLinkColor, + fontSize: 12, + ), + ), + ); + } + + return const SizedBox(); + }, + ), + ], ), extendBodyBehindAppBar: true, backgroundColor: customColors.backgroundContainerColor, diff --git a/lib/page/creative_island/list.dart b/lib/page/creative_island/list.dart deleted file mode 100644 index 9d8249ca..00000000 --- a/lib/page/creative_island/list.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:askaide/page/creative_island/box.dart'; -import 'package:askaide/repo/api/creative.dart'; -import 'package:flutter/material.dart'; -import 'package:sizer/sizer.dart'; - -/// 创作岛列表 -class CreativeIslandList extends StatelessWidget { - final List items; - final Color? color; - const CreativeIslandList({super.key, required this.items, this.color}); - - @override - Widget build(BuildContext context) { - return GridView.count( - crossAxisCount: _calCrossAxisCount(), - childAspectRatio: 1, - children: items - .map((e) => CreativeIslandBox(item: e, backgroundColor: color)) - .toList(), - ); - } - - int _calCrossAxisCount() { - if (SizerUtil.deviceType == DeviceType.tablet) { - if (SizerUtil.orientation == Orientation.landscape) { - return 4; - } - return 3; - } - - if (SizerUtil.orientation == Orientation.landscape) { - return 3; - } - return 2; - } -} diff --git a/lib/page/creative_island/creative_island_history_all.dart b/lib/page/creative_island/my_creation.dart similarity index 98% rename from lib/page/creative_island/creative_island_history_all.dart rename to lib/page/creative_island/my_creation.dart index fae813c5..19432b3d 100644 --- a/lib/page/creative_island/creative_island_history_all.dart +++ b/lib/page/creative_island/my_creation.dart @@ -20,19 +20,17 @@ import 'package:flutter_localization/flutter_localization.dart'; import 'package:go_router/go_router.dart'; import 'package:loading_more_list/loading_more_list.dart'; -class CreativeIslandHistoriesAllScreen extends StatefulWidget { +class MyCreationScreen extends StatefulWidget { final SettingRepository setting; final String mode; - const CreativeIslandHistoriesAllScreen( + const MyCreationScreen( {super.key, required this.setting, required this.mode}); @override - State createState() => - _CreativeIslandHistoriesAllScreenState(); + State createState() => _MyCreationScreenState(); } -class _CreativeIslandHistoriesAllScreenState - extends State { +class _MyCreationScreenState extends State { final DrawHistoryDatasource datasource = DrawHistoryDatasource(); @override diff --git a/lib/page/creative_island/creative_island_history_preview.dart b/lib/page/creative_island/my_creation_item.dart similarity index 97% rename from lib/page/creative_island/creative_island_history_preview.dart rename to lib/page/creative_island/my_creation_item.dart index df3f49e0..997d199c 100644 --- a/lib/page/creative_island/creative_island_history_preview.dart +++ b/lib/page/creative_island/my_creation_item.dart @@ -2,7 +2,7 @@ import 'package:askaide/bloc/creative_island_bloc.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/column_block.dart'; -import 'package:askaide/page/creative_island/content_preview.dart'; +import 'package:askaide/page/creative_island/draw/components/content_preview.dart'; import 'package:askaide/page/component/dialog.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; @@ -13,13 +13,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; -class CreativeIslandHistoryPreview extends StatefulWidget { +class MyCreationItemPage extends StatefulWidget { final String islandId; final int itemId; final SettingRepository setting; final bool showErrorMessage; - const CreativeIslandHistoryPreview({ + const MyCreationItemPage({ super.key, required this.setting, required this.islandId, @@ -28,12 +28,10 @@ class CreativeIslandHistoryPreview extends StatefulWidget { }); @override - State createState() => - _CreativeIslandHistoryPreviewState(); + State createState() => _MyCreationItemPageState(); } -class _CreativeIslandHistoryPreviewState - extends State +class _MyCreationItemPageState extends State with SingleTickerProviderStateMixin { late final TabController _tabController; diff --git a/lib/repo/api/creative.dart b/lib/repo/api/creative.dart index 10d53d93..f6577e75 100644 --- a/lib/repo/api/creative.dart +++ b/lib/repo/api/creative.dart @@ -2,6 +2,25 @@ import 'dart:convert'; import 'package:intl/intl.dart'; +class CreativeGalleryItemResponse { + CreativeGallery item; + bool isInternalUser; + + CreativeGalleryItemResponse(this.item, this.isInternalUser); + + toJson() => { + 'data': item.toJson(), + 'is_internal_user': isInternalUser, + }; + + static CreativeGalleryItemResponse fromJson(Map json) { + return CreativeGalleryItemResponse( + CreativeGallery.fromJson(json['data']), + json['is_internal_user'] ?? false, + ); + } +} + class CreativeGallery { int id; int? userId; @@ -16,6 +35,7 @@ class CreativeGallery { int refCount; int starLevel; int hotValue; + int status; DateTime? createdAt; DateTime? updatedAt; @@ -33,6 +53,7 @@ class CreativeGallery { this.refCount = 0, this.starLevel = 0, this.hotValue = 0, + this.status = 0, this.createdAt, this.updatedAt, }); @@ -72,6 +93,7 @@ class CreativeGallery { 'ref_count': refCount, 'star_level': starLevel, 'hot_value': hotValue, + 'status': status, 'created_at': createdAt?.toIso8601String(), 'updated_at': updatedAt?.toIso8601String(), }; @@ -91,6 +113,7 @@ class CreativeGallery { refCount: json['ref_count'] ?? 0, starLevel: json['star_level'] ?? 0, hotValue: json['hot_value'] ?? 0, + status: json['status'] ?? 0, createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null, diff --git a/lib/repo/api_server.dart b/lib/repo/api_server.dart index a81080a0..489fe680 100644 --- a/lib/repo/api_server.dart +++ b/lib/repo/api_server.dart @@ -1420,13 +1420,13 @@ class APIServer { ); } - Future creativeGalleryItem({ + Future creativeGalleryItem({ required int id, bool cache = true, }) async { return sendCachedGetRequest( '/v1/creatives/gallery/$id', - (resp) => CreativeGallery.fromJson(resp.data), + (resp) => CreativeGalleryItemResponse.fromJson(resp.data), forceRefresh: !cache, duration: const Duration(minutes: 30), ); From 7b5f59b47568020244268e5f6b0b859142253746 Mon Sep 17 00:00:00 2001 From: mylxsw Date: Sun, 29 Oct 2023 01:08:48 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E6=9C=AC=E5=9C=B0=20OpenAI=20=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=20BUGFIX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/bloc/chat_message_bloc.dart | 10 +++-- lib/bloc/free_count_bloc.dart | 10 +++-- lib/bloc/free_count_event.dart | 6 ++- lib/bloc/free_count_state.dart | 3 +- lib/bloc/room_bloc.dart | 10 ++--- lib/helper/ability.dart | 12 ++++-- lib/helper/error.dart | 20 ++++++--- lib/helper/model.dart | 13 ++++-- lib/lang/lang.dart | 15 +++++++ lib/page/balance/free_statistics.dart | 17 ++++++-- lib/page/balance/quota_usage_statistics.dart | 1 - lib/page/chat/component/room_item.dart | 24 +++++++++++ lib/page/chat/group/chat.dart | 3 +- lib/page/chat/group/create.dart | 2 +- lib/page/chat/group/edit.dart | 2 +- lib/page/chat/home_chat.dart | 1 + lib/page/chat/room_create.dart | 6 +-- lib/page/chat/room_edit.dart | 4 +- lib/page/chat/rooms.dart | 6 +-- lib/page/component/chat/chat_preview.dart | 25 ++++++++++- lib/page/component/chat/chat_share.dart | 2 +- lib/page/component/dialog.dart | 18 ++++---- lib/page/component/model_item.dart | 28 ++++++++++++- lib/page/creative_island/draw/draw_list.dart | 2 +- .../creative_island/gallery/gallery_item.dart | 2 +- lib/page/setting/setting_screen.dart | 2 +- lib/repo/data/room_data.dart | 1 + lib/repo/model/room.dart | 2 - lib/repo/openai_repo.dart | 41 ++++++++++++++----- 29 files changed, 220 insertions(+), 68 deletions(-) diff --git a/lib/bloc/chat_message_bloc.dart b/lib/bloc/chat_message_bloc.dart index a64657dd..44e042b6 100644 --- a/lib/bloc/chat_message_bloc.dart +++ b/lib/bloc/chat_message_bloc.dart @@ -269,7 +269,7 @@ class ChatMessageBloc extends BlocExt { // 更新 Room 最后活跃时间 // 这里没有使用 await,因为不需要等待更新完成,让 room 的更新异步的去处理吧 - if (!Ability().supportAPIServer()) { + if (!Ability().enableAPIServer()) { chatMsgRepo.updateRoomLastActiveTime(roomId); } @@ -406,11 +406,13 @@ class ChatMessageBloc extends BlocExt { chatHistory: localChatHistory, )); } catch (e) { + final error = resolveErrorMessage(e, isChat: true); await chatMsgRepo.updateMessagePart( roomId, sentMessageId, [ MessagePart('status', 2), + MessagePart('extra', jsonEncode({'error': error.toString()})), ], ); @@ -421,7 +423,7 @@ class ChatMessageBloc extends BlocExt { waitMessage.id!, Message( Role.receiver, - AppLocale.robotHasSomeError, + error.toString(), id: waitMessage.id, ts: DateTime.now(), type: MessageType.system, @@ -441,7 +443,7 @@ class ChatMessageBloc extends BlocExt { userId: APIServer().localUserID(), chatHistoryId: localChatHistoryId, ), - error: resolveErrorMessage(e), + error: error, chatHistory: localChatHistory, )); @@ -457,7 +459,7 @@ class ChatMessageBloc extends BlocExt { Future 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, diff --git a/lib/bloc/free_count_bloc.dart b/lib/bloc/free_count_bloc.dart index a22a25fe..3b45df5c 100644 --- a/lib/bloc/free_count_bloc.dart +++ b/lib/bloc/free_count_bloc.dart @@ -13,8 +13,11 @@ class FreeCountBloc extends Bloc { FreeCountBloc() : super(FreeCountInitial()) { // 重新加载所有的模型免费使用次数 on((event, emit) async { - if (Ability().supportLocalOpenAI() || !Ability().supportAPIServer()) { - emit(FreeCountLoadedState(counts: counts)); + if (!Ability().enableAPIServer()) { + emit(FreeCountLoadedState( + counts: counts, + needSignin: event.checkSigninStatus, + )); return; } @@ -24,7 +27,8 @@ class FreeCountBloc extends Bloc { // 重新加载指定模型的免费使用次数 on((event, emit) async { - if (Ability().supportLocalOpenAI() || !Ability().supportAPIServer()) { + if (Ability().usingLocalOpenAIModel(event.model) || + !Ability().enableAPIServer()) { emit(FreeCountLoadedState(counts: counts)); return; } diff --git a/lib/bloc/free_count_event.dart b/lib/bloc/free_count_event.dart index cd917f03..f509fd33 100644 --- a/lib/bloc/free_count_event.dart +++ b/lib/bloc/free_count_event.dart @@ -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}); +} diff --git a/lib/bloc/free_count_state.dart b/lib/bloc/free_count_state.dart index 453fbf91..98223fff 100644 --- a/lib/bloc/free_count_state.dart +++ b/lib/bloc/free_count_state.dart @@ -7,6 +7,7 @@ final class FreeCountInitial extends FreeCountState {} class FreeCountLoadedState extends FreeCountState { final List counts; + final bool needSignin; FreeModelCount? model(String model) { model = model.split(':').last; @@ -19,5 +20,5 @@ class FreeCountLoadedState extends FreeCountState { return null; } - FreeCountLoadedState({required this.counts}); + FreeCountLoadedState({required this.counts, this.needSignin = false}); } diff --git a/lib/bloc/room_bloc.dart b/lib/bloc/room_bloc.dart index ef342cc7..405c7b09 100644 --- a/lib/bloc/room_bloc.dart +++ b/lib/bloc/room_bloc.dart @@ -38,7 +38,7 @@ class RoomBloc extends BlocExt { )); } - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { final room = await APIServer().room(roomId: event.roomId); if (event.chatHistoryId != null && event.chatHistoryId! > 0) { final chatHistory = @@ -128,7 +128,7 @@ class RoomBloc extends BlocExt { emit(RoomsLoading()); try { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { await APIServer().createRoom( name: event.name, vendor: event.model.split(':').first, @@ -161,7 +161,7 @@ class RoomBloc extends BlocExt { emit(RoomsLoading()); try { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { await APIServer().deleteRoom(roomId: event.roomId); } else { var room = await chatMsgRepo.room(event.roomId); @@ -180,7 +180,7 @@ class RoomBloc extends BlocExt { // 更新聊天室信息 on((event, emit) async { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { final room = await APIServer().updateRoom( roomId: event.roomId, name: event.name!, @@ -311,7 +311,7 @@ class RoomBloc extends BlocExt { Future createRoomsLoadedState({bool cache = true}) async { try { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { final resp = await APIServer().rooms(cache: cache); return RoomsLoaded( resp.rooms diff --git a/lib/helper/ability.dart b/lib/helper/ability.dart index 07ba3984..2e56be09 100644 --- a/lib/helper/ability.dart +++ b/lib/helper/ability.dart @@ -22,7 +22,7 @@ class Ability { /// 是否支持 Websocket bool supportWebSocket() { - return capabilities.supportWebsocket && !supportLocalOpenAI(); + return capabilities.supportWebsocket; } /// 更新能力 @@ -95,15 +95,21 @@ class Ability { } /// 是否支持API Server - bool supportAPIServer() { + bool enableAPIServer() { return setting.stringDefault(settingAPIServerToken, '') != ''; } /// 是否启用了 OpenAI 自定义设置 - bool supportLocalOpenAI() { + bool enableLocalOpenAI() { return setting.boolDefault(settingOpenAISelfHosted, false); } + /// 是否使用本地的 OpenAI 模型 + bool usingLocalOpenAIModel(String model) { + return setting.boolDefault(settingOpenAISelfHosted, false) && + (model.startsWith('openai:') || model.startsWith('gpt-')); + } + /// 是否支持翻译功能 bool supportTranslate() { return false; diff --git a/lib/helper/error.dart b/lib/helper/error.dart index 2d96d0ea..12b301dd 100644 --- a/lib/helper/error.dart +++ b/lib/helper/error.dart @@ -2,10 +2,10 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/lang/lang.dart'; import 'package:dart_openai/openai.dart'; -Object resolveErrorMessage(dynamic e) { +Object resolveErrorMessage(dynamic e, {bool isChat = false}) { // TODO if (e is RequestFailedException) { - final msg = resolveHTTPStatusCode(e.statusCode); + final msg = resolveHTTPStatusCode(e.statusCode, isChat: isChat); if (msg != null) { return msg; } @@ -16,21 +16,31 @@ Object resolveErrorMessage(dynamic e) { return e.toString(); } -Object? resolveHTTPStatusCode(int statusCode) { +Object? resolveHTTPStatusCode(int statusCode, {bool isChat = false}) { switch (statusCode) { case 400: return const LanguageText('请求参数错误'); case 401: - if (Ability().supportLocalOpenAI()) { + if (Ability().enableLocalOpenAI()) { return const LanguageText(AppLocale.openAIAuthFailed); } - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { return const LanguageText(AppLocale.accountNeedReSignin, action: 're-signin'); } return const LanguageText(AppLocale.signInRequired, action: 'sign-in'); + case 404: + if (isChat) { + return const LanguageText(AppLocale.modelNotFound); + } + break; + case 429: + if (isChat) { + return const LanguageText(AppLocale.tooManyRequestsOrPaymentRequired); + } + return const LanguageText(AppLocale.tooManyRequests); case 451: return const LanguageText(AppLocale.modelNotValid); case 402: diff --git a/lib/helper/model.dart b/lib/helper/model.dart index 26818ed5..08a06793 100644 --- a/lib/helper/model.dart +++ b/lib/helper/model.dart @@ -19,7 +19,7 @@ class ModelAggregate { settings.stringDefault(settingAPIServerToken, '') != ''; final selfHostOpenAI = settings.boolDefault(settingOpenAISelfHosted, false); - if (isAPIServerSet && !selfHostOpenAI) { + if (isAPIServerSet) { models.addAll((await APIServer().models()) .map( (e) => mm.Model( @@ -36,8 +36,15 @@ class ModelAggregate { ), ) .toList()); - } else { - models.addAll(OpenAIRepository.supportModels()); + } + + if (selfHostOpenAI) { + return [ + ...OpenAIRepository.supportModels(), + ...models + .where((element) => element.category != modelTypeOpenAI) + .toList() + ]; } // if (isAPIServerSet || diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 5ac6fc83..3a82c68c 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -91,6 +91,7 @@ mixin AppLocale { static const String confirmToDeleteRoom = 'confirm-to-delete-room'; static const String confirmSend = 'confirm-send'; static const String openAIAuthFailed = 'openai-auth-failed'; + static const String modelNotFound = 'model-not-found'; static const String nameRequiredMessage = 'name-required-message'; static const String promptFormatError = 'prompt-format-error'; @@ -114,6 +115,8 @@ mixin AppLocale { static const String generateResult = 'generate-result'; static const String generateExitConfirm = 'generate-exit-confirm'; static const String tooManyRequests = 'too-many-requests'; + static const String tooManyRequestsOrPaymentRequired = + 'too-many-requests-or-payment-required'; static const String promptHint = 'prompt-hint'; static const String confirmClearCache = 'confirm-clear-cache'; static const String confirmSignOut = 'confirm-sign-out'; @@ -291,6 +294,7 @@ mixin AppLocale { signInRequired: '您尚未登录,请先登录', accountNeedReSignin: '账号异常,请重新登录', openAIAuthFailed: '您启用了自定义 OpenAI 服务,请检查 API Key 是否正确', + modelNotFound: '当前模型尚未开通,暂时无法使用', confirmToDeleteRoom: '确定删除?', writeYourIdeas: '你的想法', describeYourImages: '你的想法', @@ -310,6 +314,8 @@ mixin AppLocale { generateResult: '创作结果', generateExitConfirm: '创作中...\n退出后,可在历史记录中查看结果', tooManyRequests: '操作过于频繁,请稍后再试', + tooManyRequestsOrPaymentRequired: + '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', promptHint: '设定该数字人的角色和技能,以便为你提供更精准有效的信息。', confirmClearCache: '确定要清除缓存吗?', confirmSignOut: '确定要退出登录吗?', @@ -485,6 +491,8 @@ mixin AppLocale { accountNeedReSignin: 'Account exception, please log in again', openAIAuthFailed: 'You have enabled custom OpenAI service, please check if the API Key is correct', + modelNotFound: + 'The current model is not enabled yet, please try again later', confirmToDeleteRoom: 'Confirm to delete the character?', writeYourIdeas: 'Your ideas', describeYourImages: 'Your ideas', @@ -505,6 +513,8 @@ mixin AppLocale { generateExitConfirm: 'Generating...\nYou can view the result in the history', tooManyRequests: 'Too many requests, please try again later', + tooManyRequestsOrPaymentRequired: + 'Too many requests (If you are using your own OpenAI Keys, please log in to https://platform.openai.com to check if your account balance is sufficient)', promptHint: 'Set the role and skills of the character so that it can provide more accurate and effective information for you.', confirmClearCache: 'Confirm to clear cache?', @@ -601,6 +611,11 @@ class LanguageText { final String message; final String? action; const LanguageText(this.message, {this.action}); + + @override + String toString() { + return message; + } } final languages = { diff --git a/lib/page/balance/free_statistics.dart b/lib/page/balance/free_statistics.dart index 3f8e3c87..9021c982 100644 --- a/lib/page/balance/free_statistics.dart +++ b/lib/page/balance/free_statistics.dart @@ -11,6 +11,7 @@ import 'package:askaide/repo/settings_repo.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localization/flutter_localization.dart'; +import 'package:go_router/go_router.dart'; import 'package:quickalert/models/quickalert_type.dart'; class FreeStatisticsPage extends StatefulWidget { @@ -26,8 +27,9 @@ class _FreeStatisticsPageState extends State { @override void initState() { super.initState(); - - context.read().add(FreeCountReloadAllEvent()); + context + .read() + .add(FreeCountReloadAllEvent(checkSigninStatus: true)); } @override @@ -57,7 +59,16 @@ class _FreeStatisticsPageState extends State { height: double.infinity, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), - child: BlocBuilder( + child: BlocConsumer( + listenWhen: (previous, current) => + current is FreeCountLoadedState, + listener: (BuildContext context, FreeCountState state) { + if (state is FreeCountLoadedState) { + if (state.needSignin) { + context.go('/login'); + } + } + }, builder: (context, state) { if (state is FreeCountLoadedState) { if (state.counts.isEmpty) { diff --git a/lib/page/balance/quota_usage_statistics.dart b/lib/page/balance/quota_usage_statistics.dart index 22b1628a..6ada45fb 100644 --- a/lib/page/balance/quota_usage_statistics.dart +++ b/lib/page/balance/quota_usage_statistics.dart @@ -1,4 +1,3 @@ -import 'package:askaide/helper/helper.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/loading.dart'; import 'package:askaide/page/component/message_box.dart'; diff --git a/lib/page/chat/component/room_item.dart b/lib/page/chat/component/room_item.dart index 3014a63f..7627fb4c 100644 --- a/lib/page/chat/component/room_item.dart +++ b/lib/page/chat/component/room_item.dart @@ -1,4 +1,5 @@ import 'package:askaide/bloc/room_bloc.dart'; +import 'package:askaide/helper/ability.dart'; import 'package:askaide/helper/constant.dart'; import 'package:askaide/helper/haptic_feedback.dart'; import 'package:askaide/helper/helper.dart'; @@ -158,6 +159,29 @@ class RoomItem extends StatelessWidget { ), ), ), + ), + if (Ability().usingLocalOpenAIModel(room.model)) + Positioned( + right: 0, + top: 0, + child: Container( + decoration: BoxDecoration( + color: customColors.backgroundContainerColor, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(8), + bottomLeft: Radius.circular(8), + ), + ), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + child: Text( + 'local', + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 8, + ), + ), + ), ) ], ), diff --git a/lib/page/chat/group/chat.dart b/lib/page/chat/group/chat.dart index c5ef8509..c1677e63 100644 --- a/lib/page/chat/group/chat.dart +++ b/lib/page/chat/group/chat.dart @@ -10,7 +10,6 @@ import 'package:askaide/page/chat/component/group_empty.dart'; import 'package:askaide/page/component/audio_player.dart'; import 'package:askaide/page/component/background_container.dart'; import 'package:askaide/page/component/chat/chat_share.dart'; -import 'package:askaide/page/component/chat/empty.dart'; import 'package:askaide/page/component/chat/help_tips.dart'; import 'package:askaide/page/component/chat/message_state_manager.dart'; import 'package:askaide/page/component/enhanced_popup_menu.dart'; @@ -501,7 +500,7 @@ class _GroupChatPageState extends State { id: id ?? 0, size: size, usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + Ability().enableAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } diff --git a/lib/page/chat/group/create.dart b/lib/page/chat/group/create.dart index af5d4fc4..804a81a2 100644 --- a/lib/page/chat/group/create.dart +++ b/lib/page/chat/group/create.dart @@ -214,7 +214,7 @@ class _GroupCreatePageState extends State { id: id ?? 0, size: size, usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + Ability().enableAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } } diff --git a/lib/page/chat/group/edit.dart b/lib/page/chat/group/edit.dart index 7191d69e..1c602175 100644 --- a/lib/page/chat/group/edit.dart +++ b/lib/page/chat/group/edit.dart @@ -487,7 +487,7 @@ class _GroupEditPageState extends State { id: id ?? 0, size: size, usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + Ability().enableAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } diff --git a/lib/page/chat/home_chat.dart b/lib/page/chat/home_chat.dart index 34d5d430..b42d0e5a 100644 --- a/lib/page/chat/home_chat.dart +++ b/lib/page/chat/home_chat.dart @@ -324,6 +324,7 @@ class _HomeChatPageState extends State { hintText += '(今日还可免费畅享${matched.leftCount}次)'; } } + return SafeArea( child: ChatInput( enableNotifier: _inputEnabled, diff --git a/lib/page/chat/room_create.dart b/lib/page/chat/room_create.dart index 6e01b9e3..291c9b61 100644 --- a/lib/page/chat/room_create.dart +++ b/lib/page/chat/room_create.dart @@ -75,7 +75,7 @@ class _RoomCreatePageState extends State { void initState() { super.initState(); - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { APIServer().avatars().then((value) { avatarPresets = value; }); @@ -111,7 +111,7 @@ class _RoomCreatePageState extends State { maxWidth: 0, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: Ability().supportAPIServer() + child: Ability().enableAPIServer() ? SafeArea( top: false, child: DefaultTabController( @@ -287,7 +287,7 @@ class _RoomCreatePageState extends State { hintText: AppLocale.required.getString(context), textDirection: TextDirection.rtl, ), - if (Ability().supportAPIServer()) + if (Ability().enableAPIServer()) EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), title: Text( diff --git a/lib/page/chat/room_edit.dart b/lib/page/chat/room_edit.dart index 33043240..98ea4362 100644 --- a/lib/page/chat/room_edit.dart +++ b/lib/page/chat/room_edit.dart @@ -75,7 +75,7 @@ class _RoomEditPageState extends State { .add(RoomLoadEvent(widget.roomId, cascading: false)); // 获取预设头像 - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { APIServer().avatars().then((value) { avatarPresets = value; }); @@ -173,7 +173,7 @@ class _RoomEditPageState extends State { hintText: AppLocale.required.getString(context), textDirection: TextDirection.rtl, ), - if (Ability().supportAPIServer()) + if (Ability().enableAPIServer()) EnhancedInput( padding: const EdgeInsets.only(top: 10, bottom: 5), diff --git a/lib/page/chat/rooms.dart b/lib/page/chat/rooms.dart index 324b9f69..16813362 100644 --- a/lib/page/chat/rooms.dart +++ b/lib/page/chat/rooms.dart @@ -83,8 +83,8 @@ class _RoomsPageState extends State { }); }, ), - if (Ability().supportAPIServer() && - !Ability().supportLocalOpenAI()) + if (Ability().enableAPIServer() && + !Ability().enableLocalOpenAI()) EnhancedPopupMenuItem( title: '发起群聊', icon: Icons.chat_bubble_outline, @@ -184,7 +184,7 @@ class _RoomsPageState extends State { const SizedBox(width: 20), Expanded( child: EnhancedButton( - title: '添加为专属伙伴', + title: AppLocale.ok.getString(context), onPressed: () { context.read().add(GalleryRoomCopyEvent( selectedSuggestions diff --git a/lib/page/component/chat/chat_preview.dart b/lib/page/component/chat/chat_preview.dart index 2c1ee643..6b4dca4d 100644 --- a/lib/page/component/chat/chat_preview.dart +++ b/lib/page/component/chat/chat_preview.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:askaide/bloc/chat_message_bloc.dart'; import 'package:askaide/bloc/room_bloc.dart'; @@ -222,7 +223,9 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + // 消息头像 buildAvatar(message), + // 消息内容部分 ConstrainedBox( constraints: BoxConstraints( maxWidth: _chatBoxMaxWidth(context) - 80, @@ -231,15 +234,18 @@ class _ChatPreviewState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + // 发送人名称 if (message.role == Role.receiver && widget.senderNameBuilder != null) widget.senderNameBuilder!(message) ?? const SizedBox(), Wrap( crossAxisAlignment: WrapCrossAlignment.end, children: [ + // 错误指示器 if (message.role == Role.sender && message.statusIsFailed()) buildErrorIndicator(message, state, context), + // 消息主体 GestureDetector( // 选择模式下,单击切换选择与否 // 非选择模式下,单击隐藏键盘 @@ -405,12 +411,29 @@ class _ChatPreviewState extends State { HapticFeedbackHelper.mediumImpact(); + var confirmMessage = ''; + if (message.extra != null && message.extra!.isNotEmpty) { + try { + final extra = jsonDecode(message.extra!); + if (extra['error'] != null && extra['error'] != '') { + var e1 = extra['error']; + try { + e1 = (e1 as String).getString(context); + // ignore: empty_catches + } catch (ignored) {} + confirmMessage = e1; + } + // ignore: empty_catches + } catch (ignored) {} + } + openConfirmDialog( context, - AppLocale.robotHasSomeError.getString(context), + confirmMessage, () { widget.onResentEvent!(message); }, + title: Text(AppLocale.robotHasSomeError.getString(context)), confirmText: '重新发送', ); }, diff --git a/lib/page/component/chat/chat_share.dart b/lib/page/component/chat/chat_share.dart index a758deb5..cf8d3276 100644 --- a/lib/page/component/chat/chat_share.dart +++ b/lib/page/component/chat/chat_share.dart @@ -428,7 +428,7 @@ class _ChatShareScreenState extends State { id: id ?? 0, size: size, usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + Ability().enableAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } } diff --git a/lib/page/component/dialog.dart b/lib/page/component/dialog.dart index 703b126d..08ccb4cd 100644 --- a/lib/page/component/dialog.dart +++ b/lib/page/component/dialog.dart @@ -230,14 +230,18 @@ openConfirmDialog( children: [ buildBottomSheetTopBar(customColors), const SizedBox(height: 10), - Text( - message, - style: TextStyle( - color: customColors.dialogDefaultTextColor, - fontSize: 16, + if (title != null) title, + if (title != null && message != '') const SizedBox(height: 10), + if (message != '') + Text( + message, + style: TextStyle( + color: customColors.dialogDefaultTextColor, + fontSize: title == null ? 16 : 12, + ), + textAlign: TextAlign.center, + maxLines: title == null ? 4 : 2, ), - textAlign: TextAlign.center, - ), const SizedBox(height: 20), Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index a4b3c489..84a41a17 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -42,7 +42,31 @@ class ModelItem extends StatelessWidget { Expanded( child: Container( alignment: Alignment.centerLeft, - child: Text(item.name), + child: Row(children: [ + Text( + item.name, + overflow: TextOverflow.ellipsis, + ), + if (item.tag != null && item.tag!.isNotEmpty) + Container( + decoration: BoxDecoration( + color: customColors.tagsBackgroundHover, + borderRadius: BorderRadius.circular(8), + ), + margin: const EdgeInsets.only(left: 5), + padding: const EdgeInsets.symmetric( + horizontal: 5, + vertical: 2, + ), + child: Text( + item.tag!, + style: TextStyle( + fontSize: 10, + color: customColors.tagsText, + ), + ), + ), + ]), ), ), SizedBox( @@ -91,7 +115,7 @@ class ModelItem extends StatelessWidget { id: id ?? 0, size: size, usage: - Ability().supportAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, + Ability().enableAPIServer() ? AvatarUsage.room : AvatarUsage.legacy, ); } } diff --git a/lib/page/creative_island/draw/draw_list.dart b/lib/page/creative_island/draw/draw_list.dart index 21c120d7..d9c68466 100644 --- a/lib/page/creative_island/draw/draw_list.dart +++ b/lib/page/creative_island/draw/draw_list.dart @@ -24,7 +24,7 @@ class DrawListScreen extends StatefulWidget { class _DrawListScreenState extends State { @override void initState() { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { userSignedIn = true; } diff --git a/lib/page/creative_island/gallery/gallery_item.dart b/lib/page/creative_island/gallery/gallery_item.dart index f2f787cc..284da456 100644 --- a/lib/page/creative_island/gallery/gallery_item.dart +++ b/lib/page/creative_island/gallery/gallery_item.dart @@ -237,7 +237,7 @@ class _GalleryItemScreenState extends State { child: EnhancedButton( title: '制作同款', onPressed: () { - if (Ability().supportAPIServer()) { + if (Ability().enableAPIServer()) { context.push( '/creative-draw/create?mode=text-to-image&id=${state.item.creativeId}&gallery_copy_id=${state.item.id}'); } else { diff --git a/lib/page/setting/setting_screen.dart b/lib/page/setting/setting_screen.dart index afbb3332..524695bd 100644 --- a/lib/page/setting/setting_screen.dart +++ b/lib/page/setting/setting_screen.dart @@ -92,7 +92,7 @@ class _SettingScreenState extends State { // 语言设置 _buildCommonLanguageSetting(), // 常用模型 - if (Ability().supportAPIServer()) + if (Ability().enableAPIServer()) _buildCustomHomeModelsSetting(customColors), // OpenAI 自定义配置 if (Ability().enableOpenAI) diff --git a/lib/repo/data/room_data.dart b/lib/repo/data/room_data.dart index a9b10fed..7d90cd45 100644 --- a/lib/repo/data/room_data.dart +++ b/lib/repo/data/room_data.dart @@ -41,6 +41,7 @@ class RoomDataProvider { maxContext: maxContext ?? 10, createdAt: DateTime.now(), lastActiveTime: DateTime.now(), + iconData: '57683,MaterialIcons', ); room.id = await conn.insert('chat_room', room.toJson()); diff --git a/lib/repo/model/room.dart b/lib/repo/model/room.dart index e548d549..b6ecfbce 100644 --- a/lib/repo/model/room.dart +++ b/lib/repo/model/room.dart @@ -120,12 +120,10 @@ class Room { 'priority': priority, 'icon_data': iconData, 'color': color, - 'room_type': roomType, 'description': description, 'system_prompt': systemPrompt, 'init_message': initMessage, 'max_context': maxContext, - 'members': members, 'created_at': createdAt?.millisecondsSinceEpoch, 'last_active_time': lastActiveTime?.millisecondsSinceEpoch, }; diff --git a/lib/repo/openai_repo.dart b/lib/repo/openai_repo.dart index b5b17a49..169a09cb 100644 --- a/lib/repo/openai_repo.dart +++ b/lib/repo/openai_repo.dart @@ -83,40 +83,48 @@ class OpenAIRepository { static final supportForChat = { 'gpt-3.5-turbo': mm.Model( 'gpt-3.5-turbo', - 'gpt-3.5-turbo', + 'GPT-3.5 Turbo', 'openai', category: modelTypeOpenAI, isChatModel: true, - description: '能力最强的 GPT-3.5 模型,成本低', + description: '速度快,成本低', shortName: 'GPT-3.5 Turbo', + tag: 'local', + avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt35.png', ), 'gpt-3.5-turbo-16k': mm.Model( 'gpt-3.5-turbo-16k', - 'gpt-3.5-turbo-16k', + 'GPT-3.5 Turbo 16k', 'openai', category: modelTypeOpenAI, isChatModel: true, - description: '能力最强的 GPT-3.5 模型,成本为 gpt-3.5-turbo 的两倍,但是支持 4K 上下文', + description: '3.5 升级版,支持 16K 长文本', shortName: 'GPT-3.5 Turbo 16K', + tag: 'local', + avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt35.png', ), 'gpt-4': mm.Model( 'gpt-4', - 'gpt-4', + 'GPT-4', 'openai', category: modelTypeOpenAI, isChatModel: true, - description: '比GPT-3.5模型更强,能够执行复杂任务,并优化用于聊天', + description: '能力强,更精准', shortName: 'GPT-4', + tag: 'local', + avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4.png', ), 'gpt-4-32k': mm.Model( 'gpt-4-32k', - 'gpt-4-32k', + 'GPT-4 32k', 'openai', category: modelTypeOpenAI, isChatModel: true, description: '基于 GPT-4,但是支持4倍的内容长度', shortName: 'GPT-4 32K', + tag: 'local', + avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4.png', ), // 'gpt-4-0314': Model( @@ -240,14 +248,25 @@ class OpenAIRepository { void Function(ChatStreamRespData data) onData, { double temperature = 1.0, user = 'user', - model = defaultChatModel, + String model = defaultChatModel, int? roomId, int? maxTokens, }) async { var completer = Completer(); try { - if (Ability().supportWebSocket()) { + bool canUseWebsocket = true; + if (Ability().enableLocalOpenAI()) { + if (supportForChat.containsKey(model) || model.startsWith('openai:')) { + canUseWebsocket = false; + } + } + + if (!Ability().enableAPIServer()) { + canUseWebsocket = false; + } + + if (Ability().supportWebSocket() && canUseWebsocket) { final serverURL = settings.getDefault(settingServerURL, apiServerURL); final wsURL = serverURL.startsWith('https://') ? serverURL.replaceFirst('https://', 'wss://') @@ -313,7 +332,7 @@ class OpenAIRepository { 'temperature': temperature, 'user': user, 'max_tokens': maxTokens, - 'n': Ability().supportLocalOpenAI() + 'n': Ability().enableLocalOpenAI() ? null : roomId, // n 参数暂时用不到,复用作为 roomId })); @@ -324,7 +343,7 @@ class OpenAIRepository { temperature: temperature, user: user, maxTokens: maxTokens, - n: Ability().supportLocalOpenAI() + n: Ability().enableLocalOpenAI() ? null : roomId, // n 参数暂时用不到,复用作为 roomId );