mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-09-11 19:17:43 +03:00
chore: add default questions (#5457)
* chore: add default questions * chore: fix mobile ui * chore: send button * chore: send button
This commit is contained in:
parent
9c9168ac5b
commit
d5cfd054cc
@ -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),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -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,
|
||||
];
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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": {
|
||||
|
Loading…
Reference in New Issue
Block a user