chore: add default questions (#5457)

* chore: add default questions

* chore: fix mobile ui

* chore: send button

* chore: send button
This commit is contained in:
Nathan.fooo 2024-06-03 23:20:33 +08:00 committed by GitHub
parent 9c9168ac5b
commit d5cfd054cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 187 additions and 43 deletions

View File

@ -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),
),
],
);
}

View File

@ -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,
],

View File

@ -257,10 +257,9 @@ class MobileViewCard extends StatelessWidget {
],
child: BlocBuilder<ViewBloc, ViewState>(
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<MobileViewItemBottomSheetBodyAction> _buildActions(bool isFavorite) {
List<MobileViewItemBottomSheetBodyAction> _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,
];

View File

@ -413,7 +413,8 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.rename,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.duplicate,
if (state.view.layout != ViewLayoutPB.Chat)
MobileViewItemBottomSheetBodyAction.duplicate,
MobileViewItemBottomSheetBodyAction.divider,
MobileViewItemBottomSheetBodyAction.delete,
],

View File

@ -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<AIChatPage> {
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<AIChatPage> {
builder: (context, state) {
return state.initialLoadingStatus ==
const LoadingState.finish()
? const ChatWelcomePage()
? Padding(
padding: AIChatUILayout.welcomePagePadding,
child: ChatWelcomePage(
onSelectedQuestion: (question) {
blocContext
.read<ChatBloc>()
.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<AIChatPage> {
}
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,
),
),
],
),
);
}

View File

@ -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<ChatInput> {
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<ChatInput> {
),
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,
);
}
}

View File

@ -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(

View File

@ -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<String> 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,
),
],
),
),
),
),
);
}
}

View File

@ -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,

View File

@ -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": {