From d5cfd054cc8a7dad5c84aeec314d0bacc6a86a8a Mon Sep 17 00:00:00 2001 From: "Nathan.fooo" <86001920+appflowy@users.noreply.github.com> Date: Mon, 3 Jun 2024 23:20:33 +0800 Subject: [PATCH] chore: add default questions (#5457) * chore: add default questions * chore: fix mobile ui * chore: send button * chore: send button --- .../bottom_sheet_add_new_page.dart | 9 ++ .../default_mobile_action_pane.dart | 4 +- .../home/shared/mobile_view_card.dart | 10 +-- .../page_item/mobile_view_item.dart | 3 +- .../lib/plugins/ai_chat/chat_page.dart | 79 +++++++++++------- .../ai_chat/presentation/chat_input.dart | 31 ++++++- .../presentation/chat_related_question.dart | 5 +- .../presentation/chat_welcome_page.dart | 82 ++++++++++++++++++- .../lib/style_widget/icon_button.dart | 1 + frontend/resources/translations/en.json | 6 +- 10 files changed, 187 insertions(+), 43 deletions(-) diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart index 18c5c3a6df..9bd6f22880 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_add_new_page.dart @@ -55,6 +55,15 @@ class AddNewPageWidgetBottomSheet extends StatelessWidget { showTopBorder: false, onTap: () => onAction(ViewLayoutPB.Calendar), ), + FlowyOptionTile.text( + text: LocaleKeys.chat_newChat.tr(), + leftIcon: const FlowySvg( + FlowySvgs.chat_ai_page_s, + size: Size.square(18), + ), + showTopBorder: false, + onTap: () => onAction(ViewLayoutPB.Chat), + ), ], ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart index 327db627c4..17ca604717 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart @@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_slide_action_button.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -76,7 +77,8 @@ enum MobilePaneActionType { .addToFavorites, MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.rename, - MobileViewItemBottomSheetBodyAction.duplicate, + if (state.view.layout != ViewLayoutPB.Chat) + MobileViewItemBottomSheetBodyAction.duplicate, MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.delete, ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart index c4498a27cd..78f03569be 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_view_card.dart @@ -257,10 +257,9 @@ class MobileViewCard extends StatelessWidget { ], child: BlocBuilder( builder: (context, state) { - final isFavorite = state.view.isFavorite; return MobileViewItemBottomSheet( view: viewBloc.state.view, - actions: _buildActions(isFavorite), + actions: _buildActions(state.view), ); }, ), @@ -269,15 +268,16 @@ class MobileViewCard extends StatelessWidget { ); } - List _buildActions(bool isFavorite) { + List _buildActions(ViewPB view) { switch (type) { case MobileViewCardType.recent: return [ - isFavorite + view.isFavorite ? MobileViewItemBottomSheetBodyAction.removeFromFavorites : MobileViewItemBottomSheetBodyAction.addToFavorites, MobileViewItemBottomSheetBodyAction.divider, - MobileViewItemBottomSheetBodyAction.duplicate, + if (view.layout != ViewLayoutPB.Chat) + MobileViewItemBottomSheetBodyAction.duplicate, MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.removeFromRecent, ]; diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart index e21ff3be20..4b4672f6a7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart @@ -413,7 +413,8 @@ class _SingleMobileInnerViewItemState extends State { MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.rename, MobileViewItemBottomSheetBodyAction.divider, - MobileViewItemBottomSheetBodyAction.duplicate, + if (state.view.layout != ViewLayoutPB.Chat) + MobileViewItemBottomSheetBodyAction.duplicate, MobileViewItemBottomSheetBodyAction.divider, MobileViewItemBottomSheetBodyAction.delete, ], diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart index 4d103e342f..3e6142c3ec 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -25,6 +25,29 @@ import 'presentation/chat_theme.dart'; import 'presentation/chat_user_invalid_message.dart'; import 'presentation/chat_welcome_page.dart'; +class AIChatUILayout { + static EdgeInsets get chatPadding => + isMobile ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 70); + + static EdgeInsets get welcomePagePadding => isMobile + ? const EdgeInsets.symmetric(horizontal: 20) + : const EdgeInsets.symmetric(horizontal: 100); + + static double get messageWidthRatio => 0.85; + + static EdgeInsets safeAreaInsets(BuildContext context) { + final query = MediaQuery.of(context); + return isMobile + ? EdgeInsets.fromLTRB( + query.padding.left, + 0, + query.padding.right, + query.viewInsets.bottom + query.padding.bottom, + ) + : const EdgeInsets.symmetric(horizontal: 70); + } +} + class AIChatPage extends StatefulWidget { const AIChatPage({ super.key, @@ -67,7 +90,7 @@ class _AIChatPageState extends State { Widget buildChatWidget() { return SizedBox.expand( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 60), + padding: AIChatUILayout.chatPadding, child: BlocProvider( create: (context) => ChatBloc( view: widget.view, @@ -99,13 +122,22 @@ class _AIChatPageState extends State { builder: (context, state) { return state.initialLoadingStatus == const LoadingState.finish() - ? const ChatWelcomePage() + ? Padding( + padding: AIChatUILayout.welcomePagePadding, + child: ChatWelcomePage( + onSelectedQuestion: (question) { + blocContext + .read() + .add(ChatEvent.sendMessage(question)); + }, + ), + ) : const Center( child: CircularProgressIndicator.adaptive(), ); }, ), - messageWidthRatio: isMobile ? 0.8 : 0.86, + messageWidthRatio: AIChatUILayout.messageWidthRatio, bubbleBuilder: ( child, { required message, @@ -248,35 +280,26 @@ class _AIChatPageState extends State { } Widget buildChatInput(BuildContext context) { - final query = MediaQuery.of(context); - final safeAreaInsets = isMobile - ? EdgeInsets.fromLTRB( - query.padding.left, - 0, - query.padding.right, - query.viewInsets.bottom + query.padding.bottom, - ) - : const EdgeInsets.symmetric(horizontal: 70); - return Column( - children: [ - ClipRect( - child: Padding( - padding: safeAreaInsets, - child: ChatInput( + return ClipRect( + child: Padding( + padding: AIChatUILayout.safeAreaInsets(context), + child: Column( + children: [ + ChatInput( chatId: widget.view.id, onSendPressed: (message) => onSendPressed(context, message.text), ), - ), + const VSpace(6), + Opacity( + opacity: 0.6, + child: FlowyText( + LocaleKeys.chat_aiMistakePrompt.tr(), + fontSize: 12, + ), + ), + ], ), - const VSpace(6), - Opacity( - opacity: 0.6, - child: FlowyText( - LocaleKeys.chat_aiMistakePrompt.tr(), - fontSize: 12, - ), - ), - ], + ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input.dart index 732726b952..a981ab9b33 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input.dart @@ -1,6 +1,8 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -109,7 +111,7 @@ class _ChatInputState extends State { child: Padding( padding: inputPadding, child: Material( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(30), color: isMobile ? Theme.of(context).colorScheme.surfaceContainer : Theme.of(context).colorScheme.surfaceContainerHighest, @@ -169,9 +171,11 @@ class _ChatInputState extends State { ), child: Visibility( visible: _sendButtonVisible, - child: SendButton( - onPressed: _handleSendPressed, + child: Padding( padding: buttonPadding, + child: SendButton( + onPressed: _handleSendPressed, + ), ), ), ); @@ -255,3 +259,24 @@ class InputOptions { final isMobile = defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS; + +class SendButton extends StatelessWidget { + const SendButton({required this.onPressed, super.key}); + + final void Function() onPressed; + + @override + Widget build(BuildContext context) { + return FlowyIconButton( + width: 36, + fillColor: Theme.of(context).colorScheme.secondary, + radius: BorderRadius.circular(18), + icon: FlowySvg( + FlowySvgs.send_s, + size: const Size.square(24), + color: Theme.of(context).colorScheme.primary, + ), + onPressed: onPressed, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart index f4709f2263..0cf2398f68 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart @@ -71,9 +71,10 @@ class RelatedQuestionList extends StatelessWidget { padding: const EdgeInsets.all(12), child: Row( children: [ - const FlowySvg( + FlowySvg( FlowySvgs.ai_summary_generate_s, - size: Size.square(24), + size: const Size.square(24), + color: Theme.of(context).colorScheme.primary, ), const HSpace(6), FlowyText( diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart index e6fc55a15a..1014b9ef5e 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart @@ -1,10 +1,88 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; +import 'chat_input.dart'; + class ChatWelcomePage extends StatelessWidget { - const ChatWelcomePage({super.key}); + ChatWelcomePage({required this.onSelectedQuestion, super.key}); + + final void Function(String) onSelectedQuestion; + + final List items = [ + LocaleKeys.chat_question1.tr(), + LocaleKeys.chat_question2.tr(), + LocaleKeys.chat_question3.tr(), + LocaleKeys.chat_question4.tr(), + ]; + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const FlowySvg( + FlowySvgs.flowy_ai_chat_logo_s, + size: Size.square(44), + ), + const SizedBox(height: 40), + GridView.builder( + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isMobile ? 2 : 4, + crossAxisSpacing: 6, + mainAxisSpacing: 6, + childAspectRatio: 16.0 / 9.0, + ), + itemCount: items.length, + itemBuilder: (context, index) => WelcomeQuestion( + question: items[index], + onSelected: onSelectedQuestion, + ), + ), + ], + ); + } +} + +class WelcomeQuestion extends StatelessWidget { + const WelcomeQuestion({ + required this.question, + required this.onSelected, + super.key, + }); + + final void Function(String) onSelected; + final String question; @override Widget build(BuildContext context) { - return const SizedBox.shrink(); + return InkWell( + onTap: () => onSelected(question), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + child: FlowyHover( + // Make the hover effect only available on mobile + isSelected: () => isMobile, + style: HoverStyle( + borderRadius: BorderRadius.circular(6), + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText( + question, + maxLines: null, + ), + ], + ), + ), + ), + ), + ); } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart index 116158b10b..b4a2553814 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/icon_button.dart @@ -84,6 +84,7 @@ class FlowyIconButton extends StatelessWidget { richMessage: richTooltipText, showDuration: Duration.zero, child: RawMaterialButton( + clipBehavior: Clip.antiAlias, visualDensity: VisualDensity.compact, hoverElevation: 0, highlightElevation: 0, diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index a56b6ff9b3..38057a15b0 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -153,9 +153,13 @@ "unsupportedCloudPrompt": "This feature is only available when using AppFlowy Cloud", "relatedQuestion": "Related", "serverUnavailable": "Service Temporarily Unavailable. Please try again later.", - "aiServerUnavailable": "There was an error generating a response.", + "aiServerUnavailable": "🌈 Uh-oh! 🌈. A unicorn ate our response. Please retry!", "clickToRetry": "Click to retry", "regenerateAnswer": "Regenerate", + "question1": "How to use Kanban to manage tasks", + "question2": "Explain the GTD method", + "question3": "Why use Rust", + "question4": "Recipe with what's in my kitchen", "aiMistakePrompt": "AI can make mistakes. Check important info." }, "trash": {