From 8d878cb834af763bdd809f3df646916c23e12819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=A1=E5=AE=9C=E5=B0=A7?= Date: Fri, 20 Dec 2024 16:40:42 +0800 Subject: [PATCH] Some code optimization --- devtools_options.yaml | 3 + lib/bloc/chat_chat_bloc.dart | 2 +- lib/bloc/chat_chat_event.dart | 5 +- lib/lang/lang.dart | 78 ++++--- lib/page/admin/messages.dart | 21 +- lib/page/admin/models_add.dart | 137 +++++++++--- lib/page/admin/models_edit.dart | 185 +++++++++++++---- lib/page/admin/rooms.dart | 54 +++-- lib/page/chat/home.dart | 135 ++++++++---- lib/page/chat/home_chat_history.dart | 16 +- lib/page/component/account_quota_card.dart | 137 ++++++------ lib/page/component/model_item.dart | 230 +++++++++++++++++---- lib/page/drawer.dart | 38 ++-- lib/page/home.dart | 97 +++++---- lib/page/setting/destroy_account.dart | 11 +- lib/repo/api/admin/models.dart | 8 +- lib/repo/model/model.dart | 4 + macos/Runner/MainFlutterWindow.swift | 2 +- pubspec.lock | 8 + pubspec.yaml | 1 + windows/runner/main.cpp | 2 +- 21 files changed, 826 insertions(+), 348 deletions(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 00000000..fa0b357c --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/bloc/chat_chat_bloc.dart b/lib/bloc/chat_chat_bloc.dart index 517bc996..8f890b79 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, + event.count, userId: APIServer().localUserID(), ); diff --git a/lib/bloc/chat_chat_event.dart b/lib/bloc/chat_chat_event.dart index 89fbc098..de2c8204 100644 --- a/lib/bloc/chat_chat_event.dart +++ b/lib/bloc/chat_chat_event.dart @@ -3,7 +3,10 @@ part of 'chat_chat_bloc.dart'; @immutable abstract class ChatChatEvent {} -class ChatChatLoadRecentHistories extends ChatChatEvent {} +class ChatChatLoadRecentHistories extends ChatChatEvent { + final int count; + ChatChatLoadRecentHistories({this.count = 4}); +} class ChatChatNewChat extends ChatChatEvent { final String text; diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 06356d25..276e63b0 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -136,7 +136,8 @@ mixin AppLocale { static const String generating = 'generating'; 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 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'; @@ -157,7 +158,8 @@ mixin AppLocale { static const String referenceImage = 'reference-image'; static const String selectImage = 'select-image'; static const String imagination = 'imagination'; - static const String keywordsSeparatedByCommas = 'keywords-separated-by-commas'; + static const String keywordsSeparatedByCommas = + 'keywords-separated-by-commas'; static const String originalImage = 'original-image'; static const String superResolution = 'super-resolution'; static const String colorizeImage = 'colorize-image'; @@ -165,7 +167,8 @@ mixin AppLocale { static const String report = 'report'; static const String latestVersion = 'latest-version'; static const String aIdeaApp = 'aidea-app'; - static const String onceEnabledSmartOptimization = 'once-enabled-smart-optimization'; + static const String onceEnabledSmartOptimization = + 'once-enabled-smart-optimization'; static const String gotIt = 'got-it'; static const String referenceImageNote = 'reference-image-note'; static const String selectReferenceImage = 'select-reference-image'; @@ -233,6 +236,7 @@ mixin AppLocale { static const String recentlyUsed = 'recently-used'; static const String visionTag = 'vision-tag'; static const String newTag = 'new-tag'; + static const String recommendTag = 'recommend-tag'; static const String imageUploading = 'image-uploading'; static const String uploadImageLimit4 = 'upload-image-limit-4'; @@ -346,11 +350,11 @@ mixin AppLocale { selectText: '选择文本', text: '文本', uploading: '上传中...', - robotIsThinkingMessage: '数字人正在思考中...', + robotIsThinkingMessage: '正在思考中...', robotHasSomeError: '发送失败,重发该消息?', appName: 'AIdea', chatAnywhere: '聊一聊', - homeTitle: '数字人', + homeTitle: '自定义角色', creativeIsland: '创作岛', settings: '设置', language: '语言', @@ -375,8 +379,8 @@ mixin AppLocale { account: '账号', usedUp: '已用完', expired: '已过期', - character: '数字人', - createRoom: '创建数字人', + character: '角色', + createRoom: '创建角色', model: 'AI 模型', selectModel: '选择模型', roomName: '名称', @@ -403,9 +407,9 @@ mixin AppLocale { switchModel: '切换对话模型', switchModelTitle: '选择要切换的对话模型', noMessageSelected: '没有选择任何消息', - modelUsage: '模型用于设置采用的 AI 数字人类型', - promptUsage: '领域设定用于设置 AI 数字人的行为', - nameRequiredMessage: '请输入数字人名称', + modelUsage: '模型用于设置采用的 AI 角色类型', + promptUsage: '领域设定用于设置 AI 角色的行为', + nameRequiredMessage: '请输入名称', modelRequiredMessage: '请选择 AI 模型', operateSuccess: '操作成功', operateFailed: '操作失败', @@ -441,8 +445,9 @@ mixin AppLocale { generating: '创作中...', generateExitConfirm: '创作中...\n退出后,可在历史记录中查看结果', tooManyRequests: '操作过于频繁,请稍后再试', - tooManyRequestsOrPaymentRequired: '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', - promptHint: '设定该数字人的角色和技能,以便为你提供更精准有效的信息。', + tooManyRequestsOrPaymentRequired: + '操作过于频繁(如果您使用了自定义的 OpenAI Keys,请登录 https://platform.openai.com 检查账户余额是否充足)', + promptHint: '设定角色和技能,以便为你提供更精准有效的信息。', confirmClearCache: '确定要清除缓存吗?', confirmSignOut: '确定要退出登录吗?', askMeAnyQuestion: '有问题尽管问我', @@ -532,7 +537,8 @@ mixin AppLocale { others: '其它', recentlyUsed: '最近使用', visionTag: '视觉', - newTag: '新', + newTag: '上新', + recommendTag: '推荐', imageUploading: '正在上传图片,请稍后...', uploadImageLimit4: '最多只能上传 4 张图片', confirmStopOutput: '确定要停止当前输出?', @@ -643,7 +649,8 @@ mixin AppLocale { text: 'Text', uploading: 'Uploading...', robotIsThinkingMessage: 'Thinking...', - robotHasSomeError: 'There seems to be something wrong, Do you want to resend the message?', + robotHasSomeError: + 'There seems to be something wrong, Do you want to resend the message?', appName: 'AIdea', chatAnywhere: 'Chat', homeTitle: 'Characters', @@ -715,8 +722,10 @@ mixin AppLocale { modelNotValid: 'The current model is not open', signInRequired: 'You are not logged in, please log in first', 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', + 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', @@ -736,7 +745,8 @@ mixin AppLocale { generateResult: 'Generate result', generateFailed: 'Creation failed', generating: 'Generating...', - generateExitConfirm: 'Generating...\nYou can view the result in the history', + 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)', @@ -762,17 +772,21 @@ mixin AppLocale { referenceImage: 'Reference Image', selectImage: 'Select Image', imagination: 'Imagination', - keywordsSeparatedByCommas: 'Keywords of the scene you imagine, separated by commas', + keywordsSeparatedByCommas: + 'Keywords of the scene you imagine, separated by commas', originalImage: 'Original Image', superResolution: 'Super-Resolution', colorizeImage: 'Colorize Image', errorLog: 'Error Log', report: 'Report', latestVersion: 'You are currently on the latest version', - aIdeaApp: 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', - onceEnabledSmartOptimization: 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', + aIdeaApp: + 'AIdea is an app that allows you to converse with AI, and the app code is completely open source.', + onceEnabledSmartOptimization: + 'Smart Optimization\n\nOnce enabled, AI will further refine and optimize your ideas.', gotIt: 'Got it', - referenceImageNote: 'Reference Image\n\nAI will create based on the reference image provided.', + referenceImageNote: + 'Reference Image\n\nAI will create based on the reference image provided.', selectReferenceImage: 'Please select a reference image', random: 'Random', followSystem: 'Follow System', @@ -787,14 +801,17 @@ mixin AppLocale { accountInputTips: 'Enter your phone number or email', phoneInputTips: 'Enter your phone number', passwordInputTips: 'Enter your password', - pleaseReadAgreeProtocol: 'Please read and agree to the user agreement and privacy policy first', + pleaseReadAgreeProtocol: + 'Please read and agree to the user agreement and privacy policy first', signInSuccess: 'Sign in success', signInFailed: 'Sign in failed', accountRequired: 'Please enter your account', - accountFormatError: 'Account format error\nPlease enter your phone number or email', + accountFormatError: + 'Account format error\nPlease enter your phone number or email', phoneNumberFormatError: 'Phone number format error', passwordRequired: 'Please enter your password', - passwordFormatError: 'Password format error\nMust be 8-20 digits, letters, special characters', + passwordFormatError: + 'Password format error\nMust be 8-20 digits, letters, special characters', accountCreated: 'Account created', sendVerifyCode: 'Send', verifyCode: 'Verify code', @@ -814,12 +831,14 @@ mixin AppLocale { bound: 'Bound', unbind: 'Unbind', inviteCode: 'Invite code', - inviteCodeInputTips: 'Enter friend invite code, get extra rewards (optional)', + inviteCodeInputTips: + 'Enter friend invite code, get extra rewards (optional)', inviteCodeFormatError: 'Invite code format error', enableCustomOpenAI: 'Your custom OpenAI service will be used once enabled', me: 'Me', creditsUsage: 'Usage', - creditUsageTips: 'Usage details will be updated the next day, showing usage in the last 30 days.', + creditUsageTips: + 'Usage details will be updated the next day, showing usage in the last 30 days.', updateCheck: 'Check Update', buy: 'Buy', paymentHistory: 'Histories', @@ -833,6 +852,7 @@ mixin AppLocale { recentlyUsed: 'Recently Used', visionTag: 'Vision', newTag: 'New', + recommendTag: 'Recommend', imageUploading: 'Uploading image, please wait...', uploadImageLimit4: 'You can only upload up to 4 images', confirmStopOutput: 'Are you sure you want to stop current output?', @@ -864,7 +884,8 @@ mixin AppLocale { advanced: 'Advanced', collapseOptions: 'Collapse', welcomeMessage: 'Welcome Message', - welcomeMessageTips: 'The system will automatically send a welcome message each time a new chat is started.', + welcomeMessageTips: + 'The system will automatically send a welcome message each time a new chat is started.', memoryDepth: 'Memory Depth', robotRecommand: 'Recommand', pickYourRobot: 'Pick your robot', @@ -883,7 +904,8 @@ mixin AppLocale { showInviteCode: 'Show Invite Code', dontShowInviteCode: 'Don\'t Show Invite Code', inviteNow: 'Invite Now', - inviteSlogan: 'Invite friends to register, both parties will receive rewards', + inviteSlogan: + 'Invite friends to register, both parties will receive rewards', preview: 'Preview', download: 'Download', clickSwitchImage: 'Click to switch image', diff --git a/lib/page/admin/messages.dart b/lib/page/admin/messages.dart index 03811bd2..14e0079e 100644 --- a/lib/page/admin/messages.dart +++ b/lib/page/admin/messages.dart @@ -84,7 +84,7 @@ class _AdminRoomMessagesPageState extends State { } return const Text( - '用户数字人', + 'Character', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ); }, @@ -97,7 +97,9 @@ class _AdminRoomMessagesPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context.read().add(AdminRoomRecentlyMessagesLoadEvent( + context + .read() + .add(AdminRoomRecentlyMessagesLoadEvent( userId: widget.userId, roomId: widget.roomId, roomType: widget.roomType, @@ -108,13 +110,15 @@ class _AdminRoomMessagesPageState extends State { listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage(AppLocale.operateSuccess.getString(context)); + showSuccessMessage( + AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } } }, - buildWhen: (previous, current) => current is AdminRoomRecentlyMessagesLoaded, + buildWhen: (previous, current) => + current is AdminRoomRecentlyMessagesLoaded, builder: (context, state) { if (state is AdminRoomRecentlyMessagesLoaded) { return SafeArea( @@ -126,7 +130,8 @@ class _AdminRoomMessagesPageState extends State { if (e.model != null) { final model = models[e.model]; if (model != null) { - if (e.avatarUrl == null && model.avatarUrl != null) { + if (e.avatarUrl == null && + model.avatarUrl != null) { e.avatarUrl = model.avatarUrl; } @@ -139,7 +144,8 @@ class _AdminRoomMessagesPageState extends State { controller: controller, supportBloc: false, senderNameBuilder: (message) { - if (message.role == Role.sender || message.senderName == null) { + if (message.role == Role.sender || + message.senderName == null) { return null; } @@ -150,7 +156,8 @@ class _AdminRoomMessagesPageState extends State { right: 5, ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( message.senderName!, diff --git a/lib/page/admin/models_add.dart b/lib/page/admin/models_add.dart index 12c655e9..d27a730b 100644 --- a/lib/page/admin/models_add.dart +++ b/lib/page/admin/models_add.dart @@ -73,6 +73,9 @@ class _AdminModelCreatePageState extends State { /// 是否是上新 bool isNew = false; + /// 是否是推荐模型 + bool isRecommended = false; + /// Tag final TextEditingController tagController = TextEditingController(); String? tagTextColor; @@ -152,7 +155,8 @@ class _AdminModelCreatePageState extends State { } }, child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only( + left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -206,8 +210,10 @@ class _AdminModelCreatePageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced(avatarUrl!) - : FileImage(File(avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced( + avatarUrl!) + : FileImage(File( + avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -267,14 +273,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -287,14 +297,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -307,14 +321,18 @@ class _AdminModelCreatePageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -324,17 +342,22 @@ class _AdminModelCreatePageState extends State { customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: 'Subtract the expected output length from the maximum context.', + hintText: + 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -342,7 +365,8 @@ class _AdminModelCreatePageState extends State { ), ...providers.map((e) { return Container( - margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: + const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -355,16 +379,19 @@ class _AdminModelCreatePageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('At least one channel is needed'); + showErrorMessage( + 'At least one channel is needed'); return; } openConfirmDialog( context, - AppLocale.confirmToDeleteRoom.getString(context), + AppLocale.confirmToDeleteRoom + .getString(context), () { setState(() { - providers.removeWhere((item) => item == e); + providers + .removeWhere((item) => item == e); }); }, danger: true, @@ -398,7 +425,8 @@ class _AdminModelCreatePageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【System】' : ''}${e.name}'), + Text( + '${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -484,15 +512,18 @@ class _AdminModelCreatePageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: 'Whether the current model supports visual capabilities.', - confirmBtnText: AppLocale.gotIt.getString(context), + text: + 'Whether the current model supports visual capabilities.', + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -525,14 +556,16 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -548,6 +581,48 @@ class _AdminModelCreatePageState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text( + 'Recommended', + style: TextStyle(fontSize: 16), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor + ?.withAlpha(150), + ), + ), + ], + ), + CupertinoSwitch( + activeColor: customColors.linkColor, + value: isRecommended, + onChanged: (value) { + setState(() { + isRecommended = value; + }); + }, + ), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -565,14 +640,16 @@ class _AdminModelCreatePageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -630,7 +707,9 @@ class _AdminModelCreatePageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, + showAdvancedOptions + ? Icons.unfold_less + : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -677,7 +756,9 @@ class _AdminModelCreatePageState extends State { return; } - if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && + (!avatarUrl!.startsWith('http://') && + !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -688,7 +769,8 @@ class _AdminModelCreatePageState extends State { ); try { - final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting) + .upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); @@ -717,6 +799,7 @@ class _AdminModelCreatePageState extends State { tagBgColor: tagBgColor, category: categoryController.text, isNew: isNew, + isRecommend: isRecommended, ), status: modelEnabled ? 1 : 2, providers: ps, diff --git a/lib/page/admin/models_edit.dart b/lib/page/admin/models_edit.dart index f3954522..602dbac2 100644 --- a/lib/page/admin/models_edit.dart +++ b/lib/page/admin/models_edit.dart @@ -52,7 +52,8 @@ class _AdminModelEditPageState extends State { final TextEditingController maxContextController = TextEditingController(); final TextEditingController inputPriceController = TextEditingController(); final TextEditingController outputPriceController = TextEditingController(); - final TextEditingController perRequestPriceController = TextEditingController(); + final TextEditingController perRequestPriceController = + TextEditingController(); final TextEditingController promptController = TextEditingController(); final TextEditingController categoryController = TextEditingController(); @@ -71,6 +72,9 @@ class _AdminModelEditPageState extends State { /// 是否是上新 bool isNew = false; + /// 是否是推荐模型 + bool isRecommended = false; + /// Tag final TextEditingController tagController = TextEditingController(); String? tagTextColor; @@ -149,7 +153,8 @@ class _AdminModelEditPageState extends State { enabled: false, child: SingleChildScrollView( child: BlocListener( - listenWhen: (previous, current) => current is ModelOperationResult || current is ModelLoaded, + listenWhen: (previous, current) => + current is ModelOperationResult || current is ModelLoaded, listener: (context, state) { if (state is ModelOperationResult) { if (state.success) { @@ -161,10 +166,12 @@ class _AdminModelEditPageState extends State { } if (state is ModelLoaded) { - modelIdController.value = TextEditingValue(text: state.model.modelId); + modelIdController.value = + TextEditingValue(text: state.model.modelId); nameController.value = TextEditingValue(text: state.model.name); if (state.model.description != null) { - descriptionController.value = TextEditingValue(text: state.model.description!); + descriptionController.value = + TextEditingValue(text: state.model.description!); } if (state.model.avatarUrl != null) { @@ -179,30 +186,41 @@ class _AdminModelEditPageState extends State { if (state.model.meta != null) { if (state.model.meta!.maxContext != null) { - maxContextController.value = TextEditingValue(text: state.model.meta!.maxContext.toString()); + maxContextController.value = TextEditingValue( + text: state.model.meta!.maxContext.toString()); } if (state.model.meta!.inputPrice != null) { - inputPriceController.value = TextEditingValue(text: state.model.meta!.inputPrice.toString()); + inputPriceController.value = TextEditingValue( + text: state.model.meta!.inputPrice.toString()); } if (state.model.meta!.outputPrice != null) { - outputPriceController.value = TextEditingValue(text: state.model.meta!.outputPrice.toString()); + outputPriceController.value = TextEditingValue( + text: state.model.meta!.outputPrice.toString()); } if (state.model.meta!.perReqPrice != null) { - perRequestPriceController.value = TextEditingValue(text: state.model.meta!.perReqPrice.toString()); + perRequestPriceController.value = TextEditingValue( + text: state.model.meta!.perReqPrice.toString()); } - shortNameController.value = TextEditingValue(text: state.model.shortName ?? ''); - promptController.value = TextEditingValue(text: state.model.meta!.prompt ?? ''); + shortNameController.value = + TextEditingValue(text: state.model.shortName ?? ''); + promptController.value = + TextEditingValue(text: state.model.meta!.prompt ?? ''); supportVision = state.model.meta!.vision ?? false; restricted = state.model.meta!.restricted ?? false; - tagController.value = TextEditingValue(text: state.model.meta!.tag ?? ''); + tagController.value = + TextEditingValue(text: state.model.meta!.tag ?? ''); tagTextColor = state.model.meta!.tagTextColor; tagBgColor = state.model.meta!.tagBgColor; isNew = state.model.meta!.isNew ?? false; - categoryController.value = TextEditingValue(text: state.model.meta!.category ?? ''); + isRecommended = state.model.meta!.isRecommend ?? false; + categoryController.value = + TextEditingValue(text: state.model.meta!.category ?? ''); + + setState(() {}); } } @@ -211,7 +229,8 @@ class _AdminModelEditPageState extends State { }); }, child: Container( - padding: const EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 20), + padding: const EdgeInsets.only( + left: 10, right: 10, top: 10, bottom: 20), child: Column( children: [ ColumnBlock( @@ -266,8 +285,10 @@ class _AdminModelEditPageState extends State { ? null : DecorationImage( image: (avatarUrl!.startsWith('http') - ? CachedNetworkImageProviderEnhanced(avatarUrl!) - : FileImage(File(avatarUrl!))) as ImageProvider, + ? CachedNetworkImageProviderEnhanced( + avatarUrl!) + : FileImage(File( + avatarUrl!))) as ImageProvider, fit: BoxFit.cover, ), ), @@ -327,14 +348,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -347,14 +372,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/1K Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -367,14 +396,18 @@ class _AdminModelEditPageState extends State { hintText: 'Optional', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 110, alignment: Alignment.center, child: Text( 'Credits/Request', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -383,17 +416,22 @@ class _AdminModelEditPageState extends State { customColors: customColors, controller: maxContextController, textAlignVertical: TextAlignVertical.top, - hintText: 'Subtract the expected output length from the maximum context.', + hintText: + 'Subtract the expected output length from the maximum context.', showCounter: false, keyboardType: TextInputType.number, - inputFormatters: [FilteringTextInputFormatter.digitsOnly], + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly + ], textDirection: TextDirection.rtl, suffixIcon: Container( width: 50, alignment: Alignment.center, child: Text( 'Token', - style: TextStyle(color: customColors.weakTextColor, fontSize: 12), + style: TextStyle( + color: customColors.weakTextColor, + fontSize: 12), ), ), ), @@ -401,7 +439,8 @@ class _AdminModelEditPageState extends State { ), for (var i = 0; i < providers.length; i++) Container( - margin: const EdgeInsets.only(bottom: 10, left: 5, right: 5), + margin: + const EdgeInsets.only(bottom: 10, left: 5, right: 5), child: Slidable( endActionPane: ActionPane( motion: const ScrollMotion(), @@ -414,13 +453,15 @@ class _AdminModelEditPageState extends State { icon: Icons.delete, onPressed: (_) { if (providers.length == 1) { - showErrorMessage('At least one channel is needed'); + showErrorMessage( + 'At least one channel is needed'); return; } openConfirmDialog( context, - AppLocale.confirmToDeleteRoom.getString(context), + AppLocale.confirmToDeleteRoom + .getString(context), () { setState(() { providers.removeAt(i); @@ -457,7 +498,8 @@ class _AdminModelEditPageState extends State { ...modelChannels .map( (e) => SelectorItem( - Text('${e.id == null ? '【System】' : ''}${e.name}'), + Text( + '${e.id == null ? '【System】' : ''}${e.name}'), e, ), ) @@ -499,14 +541,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'When the model identifier corresponding to the channel does not match the ID here, calling the channel interface will automatically replace the model with the value configured here.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ), @@ -562,15 +606,18 @@ class _AdminModelEditPageState extends State { showBeautyDialog( context, type: QuickAlertType.info, - text: 'Whether the current model supports visual capabilities.', - confirmBtnText: AppLocale.gotIt.getString(context), + text: + 'Whether the current model supports visual capabilities.', + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -603,14 +650,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Whether to display a "New" icon next to the model to inform users that this is a new model.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -626,6 +675,48 @@ class _AdminModelEditPageState extends State { ), ], ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text( + 'Recommended', + style: TextStyle(fontSize: 16), + ), + const SizedBox(width: 5), + InkWell( + onTap: () { + showBeautyDialog( + context, + type: QuickAlertType.info, + text: + 'Whether to display a "Recommended" icon next to the model to inform users that this is a recommended model.', + confirmBtnText: + AppLocale.gotIt.getString(context), + showCancelBtn: false, + ); + }, + child: Icon( + Icons.help_outline, + size: 16, + color: customColors.weakLinkColor + ?.withAlpha(150), + ), + ), + ], + ), + CupertinoSwitch( + activeColor: customColors.linkColor, + value: isRecommended, + onChanged: (value) { + setState(() { + isRecommended = value; + }); + }, + ), + ], + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -643,14 +734,16 @@ class _AdminModelEditPageState extends State { type: QuickAlertType.info, text: 'Restricted models refer to models that cannot be used in Chinese Mainland due to policy factors.', - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, child: Icon( Icons.help_outline, size: 16, - color: customColors.weakLinkColor?.withAlpha(150), + color: customColors.weakLinkColor + ?.withAlpha(150), ), ), ], @@ -708,7 +801,9 @@ class _AdminModelEditPageState extends State { color: customColors.weakLinkColor, fontSize: 15, icon: Icon( - showAdvancedOptions ? Icons.unfold_less : Icons.unfold_more, + showAdvancedOptions + ? Icons.unfold_less + : Icons.unfold_more, color: customColors.weakLinkColor, size: 15, ), @@ -725,8 +820,10 @@ class _AdminModelEditPageState extends State { title: AppLocale.save.getString(context), onPressed: onSubmit, icon: editLocked - ? const Icon(Icons.lock, color: Colors.white, size: 16) - : const Icon(Icons.lock_open, color: Colors.white, size: 16), + ? const Icon(Icons.lock, + color: Colors.white, size: 16) + : const Icon(Icons.lock_open, + color: Colors.white, size: 16), ), ), ], @@ -757,7 +854,9 @@ class _AdminModelEditPageState extends State { return; } - if (avatarUrl != null && (!avatarUrl!.startsWith('http://') && !avatarUrl!.startsWith('https://'))) { + if (avatarUrl != null && + (!avatarUrl!.startsWith('http://') && + !avatarUrl!.startsWith('https://'))) { final cancel = BotToast.showCustomLoading( toastBuilder: (cancel) { return const LoadingIndicator( @@ -768,7 +867,8 @@ class _AdminModelEditPageState extends State { ); try { - final res = await ImageUploader(widget.setting).upload(avatarUrl!, usage: 'avatar'); + final res = await ImageUploader(widget.setting) + .upload(avatarUrl!, usage: 'avatar'); avatarUrl = res.url; } catch (e) { showErrorMessage('Failed to upload avatar'); @@ -796,6 +896,7 @@ class _AdminModelEditPageState extends State { tagTextColor: tagTextColor, tagBgColor: tagBgColor, isNew: isNew, + isRecommend: isRecommended, ), status: modelEnabled ? 1 : 2, providers: ps, diff --git a/lib/page/admin/rooms.dart b/lib/page/admin/rooms.dart index 8acfeb61..1cfa907d 100644 --- a/lib/page/admin/rooms.dart +++ b/lib/page/admin/rooms.dart @@ -33,7 +33,9 @@ class AdminRoomsPage extends StatefulWidget { class _AdminRoomsPageState extends State { @override void initState() { - context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); + context + .read() + .add(AdminRoomsLoadEvent(userId: widget.userId)); super.initState(); } @@ -45,7 +47,7 @@ class _AdminRoomsPageState extends State { appBar: AppBar( toolbarHeight: CustomSize.toolbarHeight, title: const Text( - '用户数字人列表', + 'Characters', style: TextStyle(fontSize: CustomSize.appBarTitleSize), ), centerTitle: true, @@ -57,14 +59,17 @@ class _AdminRoomsPageState extends State { child: RefreshIndicator( color: customColors.linkColor, onRefresh: () async { - context.read().add(AdminRoomsLoadEvent(userId: widget.userId)); + context + .read() + .add(AdminRoomsLoadEvent(userId: widget.userId)); }, displacement: 20, child: BlocConsumer( listener: (context, state) { if (state is AdminRoomOperationResult) { if (state.success) { - showSuccessMessage(AppLocale.operateSuccess.getString(context)); + showSuccessMessage( + AppLocale.operateSuccess.getString(context)); } else { showErrorMessage(AppLocale.operateFailed.getString(context)); } @@ -85,7 +90,8 @@ class _AdminRoomsPageState extends State { horizontal: 10, vertical: 5, ), - decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), + decoration: BoxDecoration( + borderRadius: CustomSize.borderRadius), child: Material( borderRadius: CustomSize.borderRadius, color: customColors.columnBlockBackgroundColor, @@ -103,23 +109,31 @@ class _AdminRoomsPageState extends State { _buildAvatar(room), Expanded( child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10), + padding: const EdgeInsets.symmetric( + horizontal: 10), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ Expanded( child: Text( room.name, - overflow: TextOverflow.ellipsis, + overflow: + TextOverflow.ellipsis, ), ), Text( - humanTime(room.lastActiveTime), + humanTime( + room.lastActiveTime), style: TextStyle( - color: customColors.weakLinkColor?.withAlpha(65), + color: customColors + .weakLinkColor + ?.withAlpha(65), fontSize: 10, ), ), @@ -137,13 +151,15 @@ class _AdminRoomsPageState extends State { top: 0, child: Container( decoration: BoxDecoration( - color: customColors.backgroundContainerColor, + color: customColors + .backgroundContainerColor, borderRadius: const BorderRadius.only( topRight: CustomSize.radius, bottomLeft: CustomSize.radius, ), ), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), child: Text( AppLocale.groupChat.getString(context), style: TextStyle( @@ -174,7 +190,8 @@ class _AdminRoomsPageState extends State { } Widget _buildAvatar(RoomInServer room) { - if (room.members.length == 1 && (room.avatarUrl == null || room.avatarUrl == '')) { + if (room.members.length == 1 && + (room.avatarUrl == null || room.avatarUrl == '')) { room.avatarUrl = room.members[0]; } @@ -183,7 +200,8 @@ class _AdminRoomsPageState extends State { width: 70, height: 70, child: ClipRRect( - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: CachedNetworkImageEnhanced( imageUrl: imageURL(room.avatarUrl!, qiniuImageTypeAvatar), fit: BoxFit.fill, @@ -194,7 +212,8 @@ class _AdminRoomsPageState extends State { if (room.members.isNotEmpty) { return ClipRRect( - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), child: GroupAvatar( size: 70, avatars: room.members, @@ -206,7 +225,8 @@ class _AdminRoomsPageState extends State { text: room.name.split('、').join(' '), size: 70, backgroundColor: Colors.grey.withAlpha(100), - borderRadius: const BorderRadius.only(topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), + borderRadius: const BorderRadius.only( + topLeft: CustomSize.radius, bottomLeft: CustomSize.radius), ); } } diff --git a/lib/page/chat/home.dart b/lib/page/chat/home.dart index ba422126..b9dfbdbc 100644 --- a/lib/page/chat/home.dart +++ b/lib/page/chat/home.dart @@ -90,7 +90,8 @@ class _HomePageState extends State { id: 'openai:gpt-4', supportVision: false, name: 'Chat-4', - avatarUrl: 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', + avatarUrl: + 'https://ssl.aicode.cc/ai-server/assets/avatar/gpt4-preview.png', ), ]; @@ -108,7 +109,8 @@ class _HomePageState extends State { /// 用于监听键盘事件,实现回车发送消息,Shift+Enter换行 late final FocusNode _focusNode = FocusNode( onKeyEvent: (node, event) { - if (!HardwareKeyboard.instance.isShiftPressed && event.logicalKey.keyLabel == 'Enter') { + if (!HardwareKeyboard.instance.isShiftPressed && + event.logicalKey.keyLabel == 'Enter') { if (event is KeyDownEvent) { onSubmit(context, _textController.text.trim()); } @@ -150,12 +152,14 @@ class _HomePageState extends State { Cache().boolGet(key: 'show_home_free_model_message').then((show) async { if (show) { final promotions = await APIServer().notificationPromotionEvents(); - if (promotions['chat_page'] == null || promotions['chat_page']!.isEmpty) { + if (promotions['chat_page'] == null || + promotions['chat_page']!.isEmpty) { return; } // 多个促销事件,则随机选择一个 - promotionEvent = promotions['chat_page']![Random().nextInt(promotions['chat_page']!.length)]; + promotionEvent = promotions['chat_page']![ + Random().nextInt(promotions['chat_page']!.length)]; } setState(() { @@ -240,7 +244,8 @@ class _HomePageState extends State { child: Scaffold( backgroundColor: Colors.transparent, body: BlocBuilder( - buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + buildWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, builder: (context, state) { if (state is ChatChatRecentHistoriesLoaded) { return SliverSingleComponent( @@ -256,7 +261,9 @@ class _HomePageState extends State { icon: const Icon(Icons.history), onPressed: () { context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), @@ -270,7 +277,8 @@ class _HomePageState extends State { SliverStickyHeader( header: SafeArea( top: false, - child: buildChatComponents(customColors, context, state), + child: + buildChatComponents(customColors, context, state), ), sliver: SliverList( delegate: SliverChildBuilderDelegate( @@ -280,11 +288,13 @@ class _HomePageState extends State { top: false, bottom: false, child: Container( - margin: const EdgeInsets.only(top: 10, left: 15), + margin: + const EdgeInsets.only(top: 10, left: 15), child: Text( AppLocale.histories.getString(context), style: TextStyle( - color: customColors.weakTextColor?.withAlpha(100), + color: customColors.weakTextColor + ?.withAlpha(100), fontSize: 13, ), ), @@ -298,32 +308,41 @@ class _HomePageState extends State { bottom: false, child: GestureDetector( onTap: () { - context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .push('/chat-chat/history') + .whenComplete(() { + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, child: Container( alignment: Alignment.center, - margin: const EdgeInsets.only(top: 5, bottom: 15), + margin: const EdgeInsets.only( + top: 5, bottom: 15), child: Row( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, children: [ Icon( Icons.keyboard_double_arrow_left, size: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), Text( "查看更多", style: TextStyle( fontSize: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), ), Icon( Icons.keyboard_double_arrow_right, size: 12, - color: customColors.weakTextColor!.withAlpha(120), + color: customColors.weakTextColor! + .withAlpha(120), ), ], ), @@ -343,14 +362,19 @@ class _HomePageState extends State { .push( '/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()); - context.read().add(ChatChatLoadRecentHistories()); + FocusScope.of(context) + .requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), ); }, - childCount: state.histories.isNotEmpty ? state.histories.length + 1 : 0, + childCount: state.histories.isNotEmpty + ? state.histories.length + 1 + : 0, ), ), ), @@ -467,7 +491,8 @@ class _HomePageState extends State { customColors: customColors, maxLines: inputMaxLines, minLines: 6, - hintText: AppLocale.askMeAnyQuestion.getString(context), + hintText: + AppLocale.askMeAnyQuestion.getString(context), maxLength: 150000, showCounter: false, hintColor: customColors.textfieldHintDeepColor, @@ -484,7 +509,9 @@ class _HomePageState extends State { customColors, ), ), - if (selectedImageFiles.isNotEmpty && currentModel != null && currentModel!.model.supportVision) + if (selectedImageFiles.isNotEmpty && + currentModel != null && + currentModel!.model.supportVision) SizedBox( height: 110, child: ListView( @@ -524,8 +551,10 @@ class _HomePageState extends State { child: Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - borderRadius: CustomSize.borderRadius, - color: customColors.chatRoomBackground, + borderRadius: + CustomSize.borderRadius, + color: customColors + .chatRoomBackground, ), child: Icon( Icons.close, @@ -548,10 +577,13 @@ class _HomePageState extends State { ), ), // 问题示例 - if (state.examples != null && state.examples!.isNotEmpty && state.histories.isEmpty) + if (state.examples != null && + state.examples!.isNotEmpty && + state.histories.isEmpty) Container( decoration: BoxDecoration(borderRadius: CustomSize.borderRadius), - padding: const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), + padding: + const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 3), margin: const EdgeInsets.all(10), height: 260, child: Column( @@ -580,7 +612,9 @@ class _HomePageState extends State { Expanded( child: ListView.separated( padding: const EdgeInsets.all(0), - itemCount: state.examples!.length > 4 ? 4 : state.examples!.length, + itemCount: state.examples!.length > 4 + ? 4 + : state.examples!.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return ListTextItem( @@ -596,7 +630,8 @@ class _HomePageState extends State { }, separatorBuilder: (BuildContext context, int index) { return Divider( - color: customColors.chatExampleItemText?.withAlpha(20), + color: + customColors.chatExampleItemText?.withAlpha(20), ); }, ), @@ -605,7 +640,8 @@ class _HomePageState extends State { alignment: Alignment.centerRight, child: TextButton( style: ButtonStyle( - overlayColor: WidgetStateProperty.all(Colors.transparent), + overlayColor: + WidgetStateProperty.all(Colors.transparent), ), onPressed: () { setState(() { @@ -679,19 +715,23 @@ class _HomePageState extends State { maxLines: 2, ), ), - if (promotionEvent!.clickButtonType != PromotionEventClickButtonType.none && + if (promotionEvent!.clickButtonType != + PromotionEventClickButtonType.none && promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) InkWell( onTap: () { switch (promotionEvent!.clickButtonType) { case PromotionEventClickButtonType.url: - if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { - launchUrlString(promotionEvent!.clickValue!, mode: LaunchMode.externalApplication); + if (promotionEvent!.clickValue != null && + promotionEvent!.clickValue!.isNotEmpty) { + launchUrlString(promotionEvent!.clickValue!, + mode: LaunchMode.externalApplication); } break; case PromotionEventClickButtonType.inAppRoute: - if (promotionEvent!.clickValue != null && promotionEvent!.clickValue!.isNotEmpty) { + if (promotionEvent!.clickValue != null && + promotionEvent!.clickValue!.isNotEmpty) { context.push(promotionEvent!.clickValue!); } @@ -704,7 +744,8 @@ class _HomePageState extends State { Text( '详情', style: TextStyle( - color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor( + promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), fontSize: 14, ), ), @@ -712,7 +753,8 @@ class _HomePageState extends State { Icon( Icons.keyboard_double_arrow_right, size: 16, - color: stringToColor(promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), + color: stringToColor( + promotionEvent!.clickButtonColor ?? 'FFFFFFFF'), ), ], ), @@ -765,19 +807,23 @@ class _HomePageState extends State { // 上传图片 HapticFeedbackHelper.mediumImpact(); if (selectedImageFiles.length >= 4) { - showSuccessMessage(AppLocale.uploadImageLimit4.getString(context)); + showSuccessMessage( + AppLocale.uploadImageLimit4.getString(context)); return; } - FilePickerResult? result = await FilePicker.platform.pickFiles( + FilePickerResult? result = + await FilePicker.platform.pickFiles( type: FileType.image, allowMultiple: true, ); if (result != null && result.files.isNotEmpty) { final files = selectedImageFiles; - files.addAll(result.files.map((e) => FileUpload(file: e)).toList()); + files.addAll( + result.files.map((e) => FileUpload(file: e)).toList()); setState(() { - selectedImageFiles = files.sublist(0, files.length > 4 ? 4 : files.length); + selectedImageFiles = + files.sublist(0, files.length > 4 ? 4 : files.length); }); } }, @@ -797,7 +843,8 @@ class _HomePageState extends State { child: Icon( Icons.send, color: _textController.text.trim().isNotEmpty - ? customColors.linkColor ?? const Color.fromARGB(255, 70, 165, 73) + ? customColors.linkColor ?? + const Color.fromARGB(255, 70, 165, 73) : customColors.chatInputPanelText, size: 26, ), @@ -867,7 +914,9 @@ class ChatHistoryItem extends StatelessWidget { context, AppLocale.confirmDelete.getString(context), () { - context.read().add(ChatChatDeleteHistory(history.id!)); + context + .read() + .add(ChatChatDeleteHistory(history.id!)); }, danger: true, ); @@ -880,8 +929,10 @@ class ChatHistoryItem extends StatelessWidget { borderRadius: CustomSize.borderRadius, child: InkWell( child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), - shape: RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), + contentPadding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + shape: + RoundedRectangleBorder(borderRadius: CustomSize.borderRadius), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/page/chat/home_chat_history.dart b/lib/page/chat/home_chat_history.dart index 8bdb59ab..33b1a8ab 100644 --- a/lib/page/chat/home_chat_history.dart +++ b/lib/page/chat/home_chat_history.dart @@ -19,7 +19,8 @@ class HomeChatHistoryPage extends StatefulWidget { final SettingRepository setting; final ChatMessageRepository chatMessageRepo; - const HomeChatHistoryPage({super.key, required this.setting, required this.chatMessageRepo}); + const HomeChatHistoryPage( + {super.key, required this.setting, required this.chatMessageRepo}); @override State createState() => _HomeChatHistoryPageState(); @@ -88,7 +89,8 @@ class _HomeChatHistoryPageState extends State { ), Expanded( child: BlocListener( - listenWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + listenWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, listener: (context, state) { if (state is ChatChatRecentHistoriesLoaded) { datasource.refresh(false, keyword); @@ -108,10 +110,14 @@ class _HomeChatHistoryPageState extends State { customColors: customColors, onTap: () { context - .push('/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') + .push( + '/chat-anywhere?chat_id=${item.id}&model=${item.model}&title=${item.title}') .whenComplete(() { - FocusScope.of(context).requestFocus(FocusNode()); - context.read().add(ChatChatLoadRecentHistories()); + FocusScope.of(context) + .requestFocus(FocusNode()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ); diff --git a/lib/page/component/account_quota_card.dart b/lib/page/component/account_quota_card.dart index 11d01561..42a8c43d 100644 --- a/lib/page/component/account_quota_card.dart +++ b/lib/page/component/account_quota_card.dart @@ -2,6 +2,7 @@ import 'package:askaide/helper/ability.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/credit.dart'; import 'package:askaide/page/component/enhanced_button.dart'; +import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; @@ -13,14 +14,17 @@ class AccountQuotaCard extends StatelessWidget { final UserInfo? userInfo; final VoidCallback? onPaymentReturn; final bool noBorder; - const AccountQuotaCard({super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); + const AccountQuotaCard( + {super.key, this.userInfo, this.onPaymentReturn, this.noBorder = false}); @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; return Container( - margin: noBorder ? null : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + margin: noBorder + ? null + : const EdgeInsets.symmetric(horizontal: 15, vertical: 10), height: 120, child: Container( padding: noBorder @@ -34,80 +38,77 @@ class AccountQuotaCard extends StatelessWidget { horizontal: 20, vertical: 30, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.end, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - AppLocale.usage.getString(context), - style: const TextStyle( - fontSize: 22, - color: Colors.white, - ), - ), - const SizedBox(width: 5), - InkWell( - onTap: () { - launchUrl( - Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), - ); - }, - child: const Icon( - Icons.help, - size: 16, - color: Color.fromARGB(129, 220, 220, 220), - ), - ), - ], - ), - const SizedBox(height: 15), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (userInfo != null) - Credit( - count: userInfo!.quota.quotaRemain(), - ) - else - const Text('-'), - const SizedBox(width: 5), - if (userInfo != null) + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + AppLocale.usage.getString(context), + style: TextStyle( + fontSize: 18, + color: customColors.backgroundInvertedColor, + ), + ), + const SizedBox(width: 5), InkWell( onTap: () { - context.push('/quota-usage-statistics'); + launchUrl( + Uri.parse('https://ai.aicode.cc/zhihuiguo.html'), + ); }, - child: Text( - AppLocale.creditsUsage.getString(context), - style: const TextStyle( - fontSize: 12, - color: Color.fromARGB(129, 220, 220, 220), - ), + child: Icon( + Icons.help, + size: 14, + color: customColors.weakTextColor?.withAlpha(150), ), ), - ], - ), - if (Ability().enablePayment) - EnhancedButton( - onPressed: () { - context.push('/payment').whenComplete(() { - if (onPaymentReturn != null) { - onPaymentReturn!(); - } - }); - }, - title: AppLocale.buy.getString(context), - backgroundColor: customColors.linkColor, - width: 70, - height: 35, - fontSize: 14, + ], + ), + const SizedBox(height: 15), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (userInfo != null) + InkWell( + onTap: () { + context.push('/quota-usage-statistics'); + }, + borderRadius: CustomSize.borderRadiusAll, + child: Credit( + count: userInfo!.quota.quotaRemain(), + color: customColors.backgroundInvertedColor, + ), + ) + else + const Text('-'), + const SizedBox(width: 5), + ], ), - ], + ], + ), ), + if (Ability().enablePayment) + EnhancedButton( + onPressed: () { + context.push('/payment').whenComplete(() { + if (onPaymentReturn != null) { + onPaymentReturn!(); + } + }); + }, + title: AppLocale.buy.getString(context), + backgroundColor: customColors.linkColor, + width: 70, + height: 35, + fontSize: 14, + ), ], ), ), diff --git a/lib/page/component/model_item.dart b/lib/page/component/model_item.dart index 3d9b5f33..d68b6326 100644 --- a/lib/page/component/model_item.dart +++ b/lib/page/component/model_item.dart @@ -8,6 +8,7 @@ import 'package:askaide/page/component/random_avatar.dart'; import 'package:askaide/page/component/theme/custom_size.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/model/model.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localization/flutter_localization.dart'; import 'package:quickalert/models/quickalert_type.dart'; @@ -36,6 +37,56 @@ class _ModelItemState extends State { @override Widget build(BuildContext context) { final customColors = Theme.of(context).extension()!; + + var tags = []; + + // Collect all unique tags from models + var uniqueTags = {}; + for (var model in widget.models) { + if (model.tag != null) { + uniqueTags.addAll(model.tag!.split(',').where((e) => e.isNotEmpty)); + } + + if (model.isRecommend) { + uniqueTags.add(AppLocale.recommendTag.getString(context)); + } + + if (model.isNew) { + uniqueTags.add(AppLocale.newTag.getString(context)); + } + + if (model.supportVision) { + uniqueTags.add(AppLocale.visionTag.getString(context)); + } + + if (model.modelPrice.isFree) { + uniqueTags.add(AppLocale.free.getString(context)); + } + } + + // Create tag widgets + tags = uniqueTags.map((tag) { + return InkWell( + onTap: () { + setState(() { + selectedTag = selectedTag == tag ? '' : tag; + }); + }, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + child: buildTag( + customColors, + tag, + tagBgColor: selectedTag == tag + ? colorToString(customColors.linkColor ?? Colors.green) + : colorToString(customColors.tagsBackground ?? Colors.grey), + tagTextColor: selectedTag == tag ? 'FFFFFFFF' : null, + tagFontSize: 12, + ), + ), + ); + }).toList(); + return widget.models.isNotEmpty ? Column( children: [ @@ -56,22 +107,21 @@ class _ModelItemState extends State { isDense: true, border: InputBorder.none, ), - onChanged: (value) => setState(() => keyword = value.toLowerCase()), + onChanged: (value) => + setState(() => keyword = value.toLowerCase()), ), ), + + // Tags + if (tags.isNotEmpty) + Container( + padding: const EdgeInsets.only(left: 5, right: 5, bottom: 5), + child: Row(children: tags), + ), + Expanded( child: Builder(builder: (context) { - final models = keyword.isEmpty - ? widget.models - : widget.models.where((e) { - var matchText = - e.name + (e.description ?? '') + (e.shortName ?? '') + (e.tag ?? '') + (e.category); - if (e.supportVision) { - matchText += 'vision视觉看图'; - } - - return matchText.toLowerCase().contains(keyword); - }).toList(); + final models = searchModels(); return ListView.separated( itemCount: models.length, itemBuilder: (context, i) { @@ -88,13 +138,18 @@ class _ModelItemState extends State { )); } if (item.tag != null) { - var tt = item.tag!.split(",").where((e) => e.isNotEmpty).toList(); + var tt = item.tag! + .split(",") + .where((e) => e.isNotEmpty) + .toList(); for (var i = 0; i < tt.length; i++) { tags.add(buildTag( customColors, tt[i], - tagTextColor: i == 0 ? item.tagTextColor : 'FFFFFFFF', - tagBgColor: i == 0 ? item.tagBgColor : modelTagColorSeq(i), + tagTextColor: + i == 0 ? item.tagTextColor : 'FFFFFFFF', + tagBgColor: + i == 0 ? item.tagBgColor : modelTagColorSeq(i), )); } } @@ -119,11 +174,15 @@ class _ModelItemState extends State { List separators = []; if (i == 0 && models[i].category != '') { - separators.add(buildCategory(customColors, item.category)); - } else if (i > 0 && models[i].category != models[i - 1].category) { + separators + .add(buildCategory(customColors, item.category)); + } else if (i > 0 && + models[i].category != models[i - 1].category) { separators.add(buildCategory( customColors, - item.category == '' ? AppLocale.others.getString(context) : item.category, + item.category == '' + ? AppLocale.others.getString(context) + : item.category, )); } @@ -136,51 +195,71 @@ class _ModelItemState extends State { alignment: Alignment.center, padding: const EdgeInsets.symmetric(vertical: 5), decoration: BoxDecoration( - color: widget.initValue == item.uid() ? customColors.dialogBackgroundColor : null, + color: widget.initValue == item.uid() + ? customColors.dialogBackgroundColor + : null, borderRadius: CustomSize.borderRadius, ), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.min, children: [ if (item.avatarUrl != null) ...[ - _buildAvatar(avatarUrl: item.avatarUrl, size: 50), + _buildAvatar( + avatarUrl: item.avatarUrl, size: 50), const SizedBox(width: 10), ], Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Container( alignment: - item.avatarUrl != null ? Alignment.centerLeft : Alignment.center, - child: Text( + item.avatarUrl != null + ? Alignment.centerLeft + : Alignment.center, + child: AutoSizeText( item.name, - overflow: TextOverflow.ellipsis, + minFontSize: 10, + maxFontSize: 15, + maxLines: 1, style: TextStyle( - fontSize: 15, - color: - widget.initValue == item.uid() ? customColors.linkColor : null, - fontWeight: widget.initValue == item.uid() ? FontWeight.bold : null, + color: widget.initValue == + item.uid() + ? customColors.linkColor + : null, + fontWeight: + widget.initValue == + item.uid() + ? FontWeight.bold + : null, ), ), ), ), - if (tags.length <= 3) ...formatTags(tags), + if (tags.length <= 3) + ...formatTags(tags), ], ), if (tags.length > 3) SingleChildScrollView( scrollDirection: Axis.horizontal, child: Container( - margin: const EdgeInsets.symmetric(vertical: 5), - child: Row(children: formatTags(tags)), + margin: + const EdgeInsets.symmetric( + vertical: 5), + child: Row( + children: formatTags(tags)), ), ), - if (!modelPrice.isFree) buildPriceBlock(customColors, item, modelPrice), + if (!modelPrice.isFree) + buildPriceBlock( + customColors, item, modelPrice), // if (item.description != null && item.description != '') // Text( @@ -204,7 +283,8 @@ class _ModelItemState extends State { widget.onSelected(item); }, onLongPress: () { - if (item.description == null || item.description == '') { + if (item.description == null || + item.description == '') { return; } @@ -212,7 +292,8 @@ class _ModelItemState extends State { context, type: QuickAlertType.info, text: item.description, - confirmBtnText: AppLocale.gotIt.getString(context), + confirmBtnText: + AppLocale.gotIt.getString(context), showCancelBtn: false, ); }, @@ -243,7 +324,67 @@ class _ModelItemState extends State { ); } - Widget buildPriceBlock(CustomColors customColors, Model model, ModelPrice item) { + String selectedTag = ''; + + List searchModels() { + var models = keyword.isEmpty + ? widget.models + : widget.models.where((e) { + var matchText = e.name + + (e.description ?? '') + + (e.shortName ?? '') + + (e.tag ?? '') + + (e.category); + if (e.supportVision) { + matchText += 'vision视觉看图'; + } + if (e.isNew) { + matchText += 'new新'; + } + + if (e.isRecommend) { + matchText += 'recommend推荐'; + } + + if (e.modelPrice.isFree) { + matchText += 'free免费'; + } + + return matchText.toLowerCase().contains(keyword); + }).toList(); + + if (selectedTag.isNotEmpty) { + models = models.where((e) { + var tags = []; + if (e.tag != null) { + tags = e.tag!.split(',').where((e) => e.isNotEmpty).toList(); + } + + if (e.isRecommend) { + tags.add(AppLocale.recommendTag.getString(context)); + } + + if (e.isNew) { + tags.add(AppLocale.newTag.getString(context)); + } + + if (e.supportVision) { + tags.add(AppLocale.visionTag.getString(context)); + } + + if (e.modelPrice.isFree) { + tags.add(AppLocale.free.getString(context)); + } + + return tags.contains(selectedTag); + }).toList(); + } + + return models; + } + + Widget buildPriceBlock( + CustomColors customColors, Model model, ModelPrice item) { if (item.isFree) { return const SizedBox(); } @@ -255,7 +396,8 @@ class _ModelItemState extends State { } if (item.request > 0) { - priceText += '${priceText == '' ? '' : ', '}${AppLocale.perRequest.getString(context)} ¢${item.request}'; + priceText += + '${priceText == '' ? '' : ', '}${AppLocale.perRequest.getString(context)} ¢${item.request}'; } return Row( @@ -266,8 +408,9 @@ class _ModelItemState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 10, - color: - widget.initValue == model.uid() ? customColors.linkColor : customColors.weakTextColor?.withAlpha(150), + color: widget.initValue == model.uid() + ? customColors.linkColor + : customColors.weakTextColor?.withAlpha(150), ), ), if (item.hasNote) ...[ @@ -327,11 +470,13 @@ class _ModelItemState extends State { String tag, { String? tagTextColor, String? tagBgColor, + double? tagFontSize, }) { return Container( decoration: BoxDecoration( color: tagBgColor != null - ? stringToColor(tagBgColor, defaultColor: customColors.tagsBackgroundHover ?? Colors.grey) + ? stringToColor(tagBgColor, + defaultColor: customColors.tagsBackgroundHover ?? Colors.grey) : customColors.tagsBackgroundHover, borderRadius: CustomSize.borderRadius, ), @@ -342,9 +487,10 @@ class _ModelItemState extends State { child: Text( tag, style: TextStyle( - fontSize: 8, + fontSize: tagFontSize ?? 8, color: tagTextColor != null - ? stringToColor(tagTextColor, defaultColor: customColors.tagsText ?? Colors.white) + ? stringToColor(tagTextColor, + defaultColor: customColors.tagsText ?? Colors.white) : customColors.tagsText, ), ), diff --git a/lib/page/drawer.dart b/lib/page/drawer.dart index c6dd5fe5..3c4cc704 100644 --- a/lib/page/drawer.dart +++ b/lib/page/drawer.dart @@ -3,7 +3,6 @@ import 'package:askaide/bloc/chat_chat_bloc.dart'; import 'package:askaide/helper/platform.dart'; import 'package:askaide/lang/lang.dart'; import 'package:askaide/page/component/account_quota_card.dart'; -import 'package:askaide/page/component/image.dart'; import 'package:askaide/page/component/theme/custom_theme.dart'; import 'package:askaide/repo/api/user.dart'; import 'package:flutter/material.dart'; @@ -45,21 +44,21 @@ class _LeftDrawerState extends State { child: Column( children: [ SizedBox( - height: 170, + height: 150, child: DrawerHeader( padding: PlatformTool.isMacOS() ? const EdgeInsets.only(top: kToolbarHeight) : const EdgeInsets.all(0), margin: const EdgeInsets.all(0), - decoration: BoxDecoration( - color: Colors.white, - image: DecorationImage( - image: CachedNetworkImageProviderEnhanced( - "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", - ), - fit: BoxFit.cover, - ), - ), + // decoration: BoxDecoration( + // color: Colors.white, + // image: DecorationImage( + // image: CachedNetworkImageProviderEnhanced( + // "https://ssl.aicode.cc/ai-server/assets/quota-card-bg.webp-thumb1000", + // ), + // fit: BoxFit.cover, + // ), + // ), child: BlocBuilder( builder: (_, state) { UserInfo? userInfo; @@ -72,7 +71,9 @@ class _LeftDrawerState extends State { noBorder: true, onPaymentReturn: () { if (userInfo != null) { - context.read().add(AccountLoadEvent(cache: false)); + context + .read() + .add(AccountLoadEvent(cache: false)); } }, ); @@ -82,7 +83,8 @@ class _LeftDrawerState extends State { ), const SizedBox(height: 15), BlocBuilder( - buildWhen: (previous, current) => current is ChatChatRecentHistoriesLoaded, + buildWhen: (previous, current) => + current is ChatChatRecentHistoriesLoaded, builder: (_, state) { if (state is ChatChatRecentHistoriesLoaded) { return ListView.builder( @@ -93,7 +95,8 @@ class _LeftDrawerState extends State { itemBuilder: (context, index) { final item = state.histories[index]; return ListTile( - leading: const Icon(Icons.question_answer_outlined), + leading: + const Icon(Icons.question_answer_outlined), title: Text( item.title ?? 'Unknown', maxLines: 1, @@ -116,7 +119,9 @@ class _LeftDrawerState extends State { title: Text(AppLocale.moreHistories.getString(context)), onTap: () { context.push('/chat-chat/history').whenComplete(() { - context.read().add(ChatChatLoadRecentHistories()); + context + .read() + .add(ChatChatLoadRecentHistories()); }); }, ), @@ -201,7 +206,8 @@ class _LeftDrawerState extends State { mode: LaunchMode.externalApplication, ); }, - child: Image.asset('assets/app-256-transparent.png', width: 25), + child: Image.asset('assets/app-256-transparent.png', + width: 25), ), GestureDetector( onTap: () { diff --git a/lib/page/home.dart b/lib/page/home.dart index c7c76f90..348cddde 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -34,6 +34,7 @@ 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/settings_repo.dart'; +import 'package:auto_size_text/auto_size_text.dart'; import 'package:bot_toast/bot_toast.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -63,7 +64,8 @@ class _NewHomePageState extends State { // 输入框是否可编辑 final ValueNotifier enableInput = ValueNotifier(true); // 音频播放器控制器 - final AudioPlayerController audioPlayerController = AudioPlayerController(useRemoteAPI: true); + final AudioPlayerController audioPlayerController = + AudioPlayerController(useRemoteAPI: true); // 聊天室 ID,当没有值时,会在第一个聊天消息发送后自动设置新值 int? chatId; @@ -111,7 +113,9 @@ class _NewHomePageState extends State { )); // 查询最近聊天记录 - context.read().add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); + context + .read() + .add(ChatMessageGetRecentEvent(chatHistoryId: chatId)); } } @@ -274,40 +278,24 @@ class _NewHomePageState extends State { width: MediaQuery.of(context).size.width / 2, child: Column( children: [ - // BlocBuilder( - // buildWhen: (previous, current) => current is ChatMessagesLoaded, - // builder: (context, state) { - // if (state is ChatMessagesLoaded) { - // return Text( - // state.chatHistory == null || state.chatHistory!.title == null - // ? AppLocale.chatAnywhere.getString(context) - // : state.chatHistory!.title!, - // overflow: TextOverflow.ellipsis, - // maxLines: 1, - // style: const TextStyle( - // fontSize: CustomSize.appBarTitleSize, - // ), - // ); - // } - - // return Text( - // AppLocale.chatAnywhere.getString(context), - // overflow: TextOverflow.ellipsis, - // maxLines: 1, - // style: const TextStyle(fontSize: CustomSize.appBarTitleSize), - // ); - // }, - // ), - Row( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ - Text( - selectedModel != null ? selectedModel!.name : AppLocale.selectModel.getString(context), - style: TextStyle( - fontSize: CustomSize.appBarTitleSize, - color: customColors.backgroundInvertedColor, - fontWeight: FontWeight.bold, + Flexible( + child: AutoSizeText( + selectedModel != null + ? selectedModel!.name + : AppLocale.selectModel.getString(context), + maxFontSize: 15, + minFontSize: 12, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: CustomSize.appBarTitleSize, + color: customColors.backgroundInvertedColor, + fontWeight: FontWeight.bold, + ), ), ), const SizedBox(width: 3), @@ -446,7 +434,9 @@ class _NewHomePageState extends State { label: AppLocale.stopOutput.getString(context), onPressed: () { HapticFeedbackHelper.mediumImpact(); - context.read().add(ChatMessageStopEvent()); + context + .read() + .add(ChatMessageStopEvent()); }, ), ), @@ -474,12 +464,15 @@ class _NewHomePageState extends State { enableImageUpload = currentModelV2?.supportVision ?? false; } else { var model = state.chatHistory?.model ?? room.room.model; - final cur = supportModels.where((e) => e.id == model).firstOrNull; + final cur = + supportModels.where((e) => e.id == model).firstOrNull; enableImageUpload = cur?.supportVision ?? false; } } - enableImageUpload = selectedModel == null ? enableImageUpload : (selectedModel?.supportVision ?? false); + enableImageUpload = selectedModel == null + ? enableImageUpload + : (selectedModel?.supportVision ?? false); return ChatInput( enableNotifier: enableInput, @@ -493,7 +486,8 @@ class _NewHomePageState extends State { selectedImageFiles = files; }); }, - selectedImageFiles: enableImageUpload ? selectedImageFiles : [], + selectedImageFiles: + enableImageUpload ? selectedImageFiles : [], hintText: AppLocale.askMeAnyQuestion.getString(context), onVoiceRecordTappedEvent: () { audioPlayerController.stop(); @@ -507,7 +501,8 @@ class _NewHomePageState extends State { ), // 选择模式工具栏 - if (chatPreviewController.selectMode) SelectModeToolbar(chatPreviewController: chatPreviewController), + if (chatPreviewController.selectMode) + SelectModeToolbar(chatPreviewController: chatPreviewController), ], ); } @@ -540,12 +535,15 @@ class _NewHomePageState extends State { } if (e.avatarUrl == null || e.senderName == null) { - if (loadedState.chatHistory != null && loadedState.chatHistory!.model != null) { + if (loadedState.chatHistory != null && + loadedState.chatHistory!.model != null) { if (currentModelV2 != null) { e.senderName = currentModelV2!.name; e.avatarUrl = currentModelV2!.avatarUrl; } else { - final mod = supportModels.where((e) => e.id == loadedState.chatHistory!.model!).firstOrNull; + final mod = supportModels + .where((e) => e.id == loadedState.chatHistory!.model!) + .firstOrNull; if (mod != null) { e.senderName = mod.shortName; e.avatarUrl = mod.avatarUrl; @@ -554,7 +552,9 @@ class _NewHomePageState extends State { } } - final stateMessage = room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? MessageState(); + final stateMessage = + room.states[widget.stateManager.getKey(e.roomId ?? 0, e.id ?? 0)] ?? + MessageState(); return MessageWithState(e, stateMessage); }).toList(); @@ -595,9 +595,11 @@ class _NewHomePageState extends State { }, onResetContext: () => handleResetContext(context), onResentEvent: (message, index) { - scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeOut); + scrollController.animateTo(0, + duration: const Duration(milliseconds: 500), curve: Curves.easeOut); - handleSubmit(message.text, messagetType: message.type, index: index, isResent: true); + handleSubmit(message.text, + messagetType: message.type, index: index, isResent: true); }, onSpeakEvent: (message) { audioPlayerController.playAudio(message.text); @@ -675,7 +677,10 @@ class _NewHomePageState extends State { model: selectedModel?.id, type: messagetType, chatHistoryId: chatId, - images: selectedImageFiles.where((e) => e.uploaded).map((e) => e.url!).toList(), + images: selectedImageFiles + .where((e) => e.uploaded) + .map((e) => e.url!) + .toList(), ), index: index, isResent: isResent, @@ -683,6 +688,8 @@ class _NewHomePageState extends State { ); // ignore: use_build_context_synchronously - context.read().add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); + context + .read() + .add(RoomLoadEvent(chatAnywhereRoomId, cascading: false)); } } diff --git a/lib/page/setting/destroy_account.dart b/lib/page/setting/destroy_account.dart index 2191e94b..3da5deec 100644 --- a/lib/page/setting/destroy_account.dart +++ b/lib/page/setting/destroy_account.dart @@ -27,7 +27,8 @@ class DestroyAccountScreen extends StatefulWidget { class _DestroyAccountScreenState extends State { final TextEditingController _passwordController = TextEditingController(); - final TextEditingController _verificationCodeController = TextEditingController(); + final TextEditingController _verificationCodeController = + TextEditingController(); String verifyCodeId = ''; @@ -61,12 +62,13 @@ class _DestroyAccountScreenState extends State { children: [ const MessageBox( message: - '请注意,注销账号后:\n1. 您的数据将被清空,包括数字人、创作岛历史纪录、充值数据、智慧果使用明细等全部数据;\n2. 您未使用完的智慧果将会被销毁,无法继续使用,无法退回;\n3. 注销操作不可逆,一旦账号注销,所有被删除数据均无法恢复。', + '请注意,注销账号后:\n1. 您的数据将被清空,包括角色、创作岛历史纪录、充值数据、智慧果使用明细等全部数据;\n2. 您未使用完的智慧果将会被销毁,无法继续使用,无法退回;\n3. 注销操作不可逆,一旦账号注销,所有被删除数据均无法恢复。', type: MessageBoxType.warning, ), const SizedBox(height: 15), ColumnBlock( - padding: const EdgeInsets.only(top: 20, left: 10, right: 10, bottom: 20), + padding: const EdgeInsets.only( + top: 20, left: 10, right: 10, bottom: 20), children: [ VerifyCodeInput( inColumnBlock: false, @@ -87,7 +89,8 @@ class _DestroyAccountScreenState extends State { Container( height: 45, width: double.infinity, - decoration: BoxDecoration(color: Colors.red, borderRadius: CustomSize.borderRadius), + decoration: BoxDecoration( + color: Colors.red, borderRadius: CustomSize.borderRadius), child: TextButton( onPressed: onDestroySubmit, child: Text( diff --git a/lib/repo/api/admin/models.dart b/lib/repo/api/admin/models.dart index 938b76e3..258cd1c0 100644 --- a/lib/repo/api/admin/models.dart +++ b/lib/repo/api/admin/models.dart @@ -35,7 +35,9 @@ class AdminModel { avatarUrl: json['avatar_url'], status: json['status'], meta: json['meta'] != null ? AdminModelMeta.fromJson(json['meta']) : null, - providers: ((json['providers'] ?? []) as List).map((e) => AdminModelProvider.fromJson(e)).toList(), + providers: ((json['providers'] ?? []) as List) + .map((e) => AdminModelProvider.fromJson(e)) + .toList(), ); } @@ -67,6 +69,7 @@ class AdminModelMeta { String? tagBgColor; bool? isNew; + bool? isRecommend; String? category; AdminModelMeta({ @@ -81,6 +84,7 @@ class AdminModelMeta { this.tagTextColor, this.tagBgColor, this.isNew, + this.isRecommend, this.category, }); @@ -97,6 +101,7 @@ class AdminModelMeta { tagTextColor: json['tag_text_color'], tagBgColor: json['tag_bg_color'], isNew: json['is_new'] ?? false, + isRecommend: json['is_recommend'] ?? false, category: json['category'], ); } @@ -114,6 +119,7 @@ class AdminModelMeta { 'tag_text_color': tagTextColor, 'tag_bg_color': tagBgColor, 'is_new': isNew, + 'is_recommend': isRecommend, 'category': category, }; } diff --git a/lib/repo/model/model.dart b/lib/repo/model/model.dart index f94e2323..3b23fdfe 100644 --- a/lib/repo/model/model.dart +++ b/lib/repo/model/model.dart @@ -16,6 +16,7 @@ class Model { String? tagTextColor; String? tagBgColor; bool isNew = false; + bool isRecommend = false; String category; bool isDefault; @@ -36,6 +37,7 @@ class Model { this.tagTextColor, this.tagBgColor, this.isNew = false, + this.isRecommend = false, this.isDefault = false, }); @@ -58,6 +60,7 @@ class Model { String? tagTextColor, String? tagBgColor, bool? isNew, + bool? isRecommend, String? category, bool? isDefault, }) { @@ -76,6 +79,7 @@ class Model { tagTextColor: tagTextColor ?? this.tagTextColor, tagBgColor: tagBgColor ?? this.tagBgColor, isNew: isNew ?? this.isNew, + isRecommend: isRecommend ?? this.isRecommend, category: category ?? this.category, isDefault: isDefault ?? false, ); diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift index 2ac77f5c..a8b21e7d 100644 --- a/macos/Runner/MainFlutterWindow.swift +++ b/macos/Runner/MainFlutterWindow.swift @@ -9,7 +9,7 @@ class MainFlutterWindow: NSWindow { self.setFrame(windowFrame, display: true) // 设置窗口大小 - self.setContentSize(NSSize(width: 400, height: 800)) + self.setContentSize(NSSize(width: 850, height: 750)) // 设置窗口禁止缩放 // let window: NSWindow! = self.contentView?.window diff --git a/pubspec.lock b/pubspec.lock index f6bcad4d..7be64cd1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + auto_size_text: + dependency: "direct main" + description: + name: auto_size_text + sha256: "3f5261cd3fb5f2a9ab4e2fc3fba84fd9fcaac8821f20a1d4e71f557521b22599" + url: "https://pub.dev" + source: hosted + version: "3.0.0" autoscale_tabbarview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 95dff37d..d368f3f9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -133,6 +133,7 @@ dependencies: camera: ^0.11.0+2 camerawesome: ^2.1.0 flutter_markdown_latex: ^0.3.4 + auto_size_text: ^3.0.0 dev_dependencies: flutter_test: diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp index d85164e5..95eeb04a 100644 --- a/windows/runner/main.cpp +++ b/windows/runner/main.cpp @@ -26,7 +26,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); - Win32Window::Size size(400, 800); + Win32Window::Size size(850, 750); if (!window.Create(L"AIdea", origin, size)) { return EXIT_FAILURE; }