2261 lines
127 KiB
Dart
2261 lines
127 KiB
Dart
// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously
|
|
|
|
import 'dart:math';
|
|
|
|
import 'package:cross_file/cross_file.dart';
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:hoshan/core/gen/assets.gen.dart';
|
|
import 'package:hoshan/core/gen/my_flutter_app_icons.dart';
|
|
import 'package:hoshan/core/routes/route_generator.dart';
|
|
import 'package:hoshan/core/services/file_manager/download_file_services.dart';
|
|
import 'package:hoshan/core/services/file_manager/pick_file_services.dart';
|
|
import 'package:hoshan/core/utils/date_time.dart';
|
|
import 'package:hoshan/core/utils/strings.dart';
|
|
import 'package:hoshan/data/model/ai/chats_history_model.dart';
|
|
import 'package:hoshan/data/model/ai/credit_model.dart';
|
|
import 'package:hoshan/data/model/ai/messages_model.dart';
|
|
import 'package:hoshan/data/model/ai/send_message_model.dart';
|
|
import 'package:hoshan/data/model/chat_args.dart';
|
|
import 'package:hoshan/data/model/empty_states_enum.dart';
|
|
import 'package:hoshan/ui/screens/chat/bloc/messages_bloc.dart';
|
|
import 'package:hoshan/ui/screens/chat/bloc/related_questions_bloc.dart';
|
|
import 'package:hoshan/ui/screens/chat/cubit/receive_message_cubit.dart';
|
|
import 'package:hoshan/ui/screens/library/bloc/chats_history_bloc.dart';
|
|
import 'package:hoshan/ui/screens/library/library_screen.dart';
|
|
import 'package:hoshan/ui/screens/splash/cubit/user_info_cubit.dart';
|
|
import 'package:hoshan/ui/theme/colors.dart';
|
|
import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart';
|
|
import 'package:hoshan/ui/theme/responsive.dart';
|
|
import 'package:hoshan/ui/theme/text.dart';
|
|
import 'package:hoshan/ui/widgets/components/animations/animated_visibility.dart';
|
|
import 'package:hoshan/ui/widgets/components/audio/player.dart';
|
|
import 'package:hoshan/ui/widgets/components/audio/recorder.dart';
|
|
import 'package:hoshan/ui/widgets/components/button/circle_icon_btn.dart';
|
|
import 'package:hoshan/ui/screens/chat/cubit/like_message_cubit.dart';
|
|
import 'package:hoshan/ui/widgets/components/dialog/bottom_sheets.dart';
|
|
import 'package:hoshan/ui/widgets/components/dialog/dialog_handler.dart';
|
|
import 'package:hoshan/ui/widgets/components/dropdown/hint_tooltip.dart';
|
|
import 'package:hoshan/ui/widgets/components/dropdown/more_popup_menu.dart';
|
|
import 'package:hoshan/ui/widgets/components/image/custome_image.dart';
|
|
import 'package:hoshan/ui/widgets/components/image/network_image.dart';
|
|
import 'package:hoshan/ui/widgets/components/snackbar/snackbar_manager.dart';
|
|
import 'package:hoshan/ui/widgets/components/text/default_markdown_text.dart';
|
|
import 'package:hoshan/ui/widgets/components/video/video_thumbnail.dart';
|
|
import 'package:hoshan/ui/widgets/sections/empty/empty_states.dart';
|
|
import 'package:hoshan/ui/widgets/sections/loading/chat_screen_placeholder.dart';
|
|
import 'package:hoshan/ui/widgets/sections/loading/default_placeholder.dart';
|
|
|
|
class ChatPage extends StatefulWidget {
|
|
final ChatArgs chatArgs;
|
|
const ChatPage({super.key, required this.chatArgs});
|
|
|
|
@override
|
|
State<ChatPage> createState() => _ChatPageState();
|
|
}
|
|
|
|
class _ChatPageState extends State<ChatPage> {
|
|
late int? chatId = widget.chatArgs.chatId;
|
|
late final bot = widget.chatArgs.bot;
|
|
late final ValueNotifier<bool> visibleAttach =
|
|
ValueNotifier(widget.chatArgs.bot.attachment == 3);
|
|
final ValueNotifier<bool> showRecorder = ValueNotifier(false);
|
|
final ValueNotifier<bool> isGhost = ValueNotifier(false);
|
|
final ValueNotifier<bool> recording = ValueNotifier(false);
|
|
final ValueNotifier<bool> refreshQuestions = ValueNotifier(true);
|
|
final ValueNotifier<bool> webSearch = ValueNotifier(false);
|
|
final ValueNotifier<bool> showInfo = ValueNotifier(false);
|
|
final ValueNotifier<XFile?> selectedFile = ValueNotifier(null);
|
|
final TextEditingController messageText = TextEditingController();
|
|
ValueNotifier<int?> maxLines = ValueNotifier(5);
|
|
|
|
void sendRequest(
|
|
{required final String? message,
|
|
final bool retry = false,
|
|
final XFile? file,
|
|
final bool withOutNewMessage = false}) {
|
|
final creditState = CreditModel(
|
|
credit: UserInfoCubit.userInfoModel.credit ?? 0,
|
|
freeCredit: UserInfoCubit.userInfoModel.freeCredit ?? 0);
|
|
int credit = (creditState.freeCredit ?? 0) + (creditState.credit ?? 0);
|
|
if (credit < widget.chatArgs.bot.cost!) {
|
|
DialogHandler(context: context).showUpgradeCredit();
|
|
messageText.text = message ?? '';
|
|
|
|
return;
|
|
}
|
|
|
|
if (!withOutNewMessage) {
|
|
context.read<MessagesBloc>().add(AddMessage(
|
|
message: Messages(
|
|
role: 'human',
|
|
id: 'hero',
|
|
content: [
|
|
if (message != null && message.isNotEmpty)
|
|
Content(type: 'text', text: message)
|
|
],
|
|
query: message,
|
|
file: file,
|
|
retry: retry)));
|
|
}
|
|
|
|
context.read<ReceiveMessageCubit>().execute(
|
|
request: SendMessageModel(
|
|
botId: bot.id,
|
|
file: file,
|
|
id: chatId,
|
|
messageId: 'hero',
|
|
query: message,
|
|
ghost: isGhost.value,
|
|
tool: bot.tool,
|
|
webSearch: webSearch.value,
|
|
retry: retry));
|
|
if (widget.chatArgs.bot.attachment != 3 && refreshQuestions.value) {
|
|
context.read<RelatedQuestionsBloc>().add(ClearAllRelatedQuestions());
|
|
}
|
|
|
|
selectedFile.value = null;
|
|
showRecorder.value = false;
|
|
messageText.clear();
|
|
}
|
|
|
|
void _popUpMenu(
|
|
Messages message, GlobalKey<State<StatefulWidget>> containerKey) {
|
|
String copyText = '';
|
|
List<String> urls = [];
|
|
|
|
if (message.content != null && message.content!.isNotEmpty) {
|
|
for (var content in message.content!) {
|
|
if (content.imageUrl != null) {
|
|
if (content.imageUrl!.url != null) {
|
|
urls.add(content.imageUrl!.url!);
|
|
}
|
|
copyText += content.imageUrl!.query ?? '';
|
|
copyText += ' ';
|
|
}
|
|
if (content.audioUrl != null) {
|
|
if (content.audioUrl!.url != null) {
|
|
urls.add(content.audioUrl!.url!);
|
|
}
|
|
copyText += content.audioUrl!.query ?? '';
|
|
copyText += ' ';
|
|
}
|
|
if (content.pdfUrl != null) {
|
|
if (content.pdfUrl!.url != null) {
|
|
urls.add(content.pdfUrl!.url!);
|
|
}
|
|
copyText += content.pdfUrl!.query ?? '';
|
|
copyText += ' ';
|
|
}
|
|
if (content.pdfUrl == null &&
|
|
content.imageUrl == null &&
|
|
content.audioUrl == null) {
|
|
copyText += content.text ?? '';
|
|
copyText += ' ';
|
|
}
|
|
}
|
|
}
|
|
final items = [
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 0,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
color: message.fromBot!
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.white,
|
|
icon: Assets.icon.outline.trash,
|
|
title: 'حذف')),
|
|
click: () {
|
|
try {
|
|
context
|
|
.read<MessagesBloc>()
|
|
.add(DeleteMessage(chatId: chatId!, message: message));
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print("Error when delete message: $e");
|
|
}
|
|
}
|
|
},
|
|
),
|
|
if ((bot.deleted != null && !bot.deleted!))
|
|
if (!message.fromBot!)
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 1,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
color: message.fromBot!
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.white,
|
|
icon: Assets.icon.outline.bitcoinRefresh,
|
|
title: 'دوباره بپرس')),
|
|
click: () {
|
|
if (message.content != null &&
|
|
message.content!
|
|
.firstWhere(
|
|
(element) => element.type == 'text',
|
|
)
|
|
.text !=
|
|
null) {
|
|
refreshQuestions.value = false;
|
|
sendRequest(
|
|
file: message.file,
|
|
message: message.content!
|
|
.firstWhere(
|
|
(element) => element.type == 'text',
|
|
)
|
|
.text!,
|
|
retry: true);
|
|
}
|
|
},
|
|
),
|
|
if (copyText.replaceAll(' ', '').isNotEmpty)
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 2,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
color: message.fromBot!
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.white,
|
|
icon: Assets.icon.outline.copy,
|
|
title: 'کپی')),
|
|
click: () async {
|
|
await Clipboard.setData(ClipboardData(text: copyText));
|
|
if (mounted) {
|
|
SnackBarManager(context, id: 'Copy').show(
|
|
status: SnackBarStatus.success,
|
|
message: 'پیام با موفقیت کپی شد 😃',
|
|
);
|
|
}
|
|
},
|
|
),
|
|
];
|
|
MorePopupMenuHandler(context: context).showMorePopupMenu(
|
|
right: message.fromBot!,
|
|
color: message.error!
|
|
? AppColors.red.defaultShade
|
|
: message.fromBot!
|
|
? Theme.of(context).colorScheme.surface
|
|
: AppColors.primaryColor.defaultShade,
|
|
containerKey: containerKey,
|
|
items: [
|
|
...items,
|
|
if (urls.isNotEmpty)
|
|
for (var i = 0; i < urls.length; i++)
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: i + items.length,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
color: message.fromBot!
|
|
? Theme.of(context).colorScheme.primary
|
|
: Colors.white,
|
|
icon: Assets.icon.outline.download,
|
|
title:
|
|
'دانلود ${urls[i].isVideo() ? "ویدیو" : urls[i].isImage() ? 'عکس' : urls[i].isAudio() ? 'فایل صوتی' : 'فایل'}')),
|
|
click: () async {
|
|
DownloadFileService.getFile(url: urls[i]).then((value) {
|
|
SnackBarManager(context).show(
|
|
message: 'فایل با موفقیت در پوشه Downloads نشست.',
|
|
status: SnackBarStatus.success);
|
|
});
|
|
},
|
|
),
|
|
]);
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
context.read<ChatsHistoryBloc>().add(RestartChatsHistory());
|
|
|
|
context.read<ChatsHistoryBloc>().add(
|
|
GetAllChats(type: widget.chatArgs.isPerson ? 'character' : 'llm'));
|
|
});
|
|
}
|
|
|
|
late double maxWidthDesktop;
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Theme(
|
|
data: Theme.of(context).copyWith(
|
|
bottomSheetTheme: const BottomSheetThemeData(
|
|
surfaceTintColor: Colors.transparent,
|
|
backgroundColor: Colors.transparent)),
|
|
child: Scaffold(
|
|
drawer: Drawer(
|
|
shape: const BeveledRectangleBorder(borderRadius: BorderRadius.zero),
|
|
child: LibraryScreen(
|
|
type: widget.chatArgs.isPerson ? 'character' : 'llm',
|
|
onTap: (chat) async {
|
|
context.push(Routes.chat,
|
|
extra: ChatArgs(
|
|
bot: chat.bot!,
|
|
chatId: chat.id,
|
|
isPerson: widget.chatArgs.isPerson));
|
|
},
|
|
),
|
|
),
|
|
appBar: AppBar(
|
|
// actions: [
|
|
// InkWell(
|
|
// onTap: () {
|
|
// DialogHandler(context: context).showPrivateBots();
|
|
// },
|
|
// child: CircleIconBtn(
|
|
// size: Responsive(context).isMobile() ? 32 : 46,
|
|
// iconPadding: const EdgeInsets.all(8),
|
|
// icon: Assets.icon.outline.crown,
|
|
// color: context.read<ThemeModeCubit>().isDark()
|
|
// ? AppColors.black[900]
|
|
// : AppColors.secondryColor[50],
|
|
// iconColor: Theme.of(context).colorScheme.secondary,
|
|
// ),
|
|
// ),
|
|
// const SizedBox(
|
|
// width: 16,
|
|
// ),
|
|
// ],
|
|
leading: Builder(builder: (context) {
|
|
return IconButton(
|
|
icon: const Icon(Icons.menu),
|
|
onPressed: () {
|
|
Scaffold.of(context).openDrawer();
|
|
},
|
|
);
|
|
}),
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: MediaQuery.sizeOf(context).height,
|
|
child: Assets.image.chatBack.image(fit: BoxFit.cover),
|
|
),
|
|
Responsive(context).maxWidthInDesktop(
|
|
maxWidth: 800,
|
|
child: (contxet, maxWidth) {
|
|
maxWidthDesktop = maxWidth;
|
|
return SingleChildScrollView(
|
|
controller: ReceiveMessageCubit.scrollController,
|
|
reverse: true,
|
|
physics:
|
|
context.watch<MessagesBloc>().state is MessagesLoading
|
|
? const NeverScrollableScrollPhysics()
|
|
: const BouncingScrollPhysics(),
|
|
child: Column(
|
|
children: [
|
|
messages(),
|
|
aNewMessage(),
|
|
if (widget.chatArgs.bot.attachment != 3)
|
|
relatedQuestions(),
|
|
const SizedBox(
|
|
height: 120,
|
|
),
|
|
ValueListenableBuilder(
|
|
valueListenable: selectedFile,
|
|
builder: (context, value, child) => SizedBox(
|
|
height: value != null ? 70 : 0,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
bottomSheet: messageBar(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget messages() {
|
|
return BlocConsumer<MessagesBloc, MessagesState>(
|
|
listener: (context, state) {
|
|
if (state is MessagesSuccess) {
|
|
if (state.isGetAll) {
|
|
if (chatId != null &&
|
|
widget.chatArgs.bot.attachment != 3 &&
|
|
(widget.chatArgs.bot.tool != null &&
|
|
!widget.chatArgs.bot.tool!)) {
|
|
try {
|
|
if (refreshQuestions.value) {
|
|
context
|
|
.read<RelatedQuestionsBloc>()
|
|
.add(GetAllRelatedQuestions(
|
|
chatId: chatId!,
|
|
messageId: state.messages
|
|
.lastWhere(
|
|
(element) => element.role == 'human',
|
|
)
|
|
.id!,
|
|
content: state.messages.last.query ?? '',
|
|
));
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print("Error while get Related Questions is: $e");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
builder: (context, state) {
|
|
if (state is MessagesFail) {
|
|
return Padding(
|
|
padding:
|
|
EdgeInsets.only(top: MediaQuery.sizeOf(context).height * 0.1),
|
|
child: EmptyStates.getEmptyState(status: EmptyStatesEnum.server),
|
|
);
|
|
}
|
|
if (state is MessagesLoading) {
|
|
return const ChatScreenPlaceholder();
|
|
}
|
|
if (state is MessagesSuccess) {
|
|
return ListView.builder(
|
|
shrinkWrap: true,
|
|
itemCount: state.messages.length,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemBuilder: (context, index) {
|
|
final message = state.messages[index];
|
|
final GlobalKey containerKey = GlobalKey();
|
|
final GlobalKey<DefaultMarkdownTextState> markdownKey =
|
|
GlobalKey();
|
|
ValueNotifier<String> directionName = ValueNotifier('RTL');
|
|
return GestureDetector(
|
|
onLongPress: () {
|
|
_popUpMenu(message, containerKey);
|
|
},
|
|
child: Container(
|
|
alignment: message.fromBot!
|
|
? Alignment.centerLeft
|
|
: Alignment.centerRight,
|
|
padding: const EdgeInsets.all(16),
|
|
child: Directionality(
|
|
textDirection: message.fromBot!
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Container(
|
|
constraints: BoxConstraints(
|
|
minWidth: Responsive(context).isMobile()
|
|
? maxWidthDesktop * 0.4
|
|
: maxWidthDesktop * 0.2,
|
|
maxWidth: Responsive(context).isMobile()
|
|
? maxWidthDesktop * 0.8
|
|
: maxWidthDesktop * 0.6),
|
|
decoration: BoxDecoration(
|
|
color: message.error!
|
|
? AppColors.red.defaultShade
|
|
: message.fromBot!
|
|
? Theme.of(context)
|
|
.colorScheme
|
|
.surface
|
|
: AppColors.primaryColor.defaultShade,
|
|
borderRadius: BorderRadius.circular(16)
|
|
.copyWith(
|
|
topRight: message.fromBot!
|
|
? const Radius.circular(10)
|
|
: const Radius.circular(0),
|
|
bottomLeft: message.fromBot!
|
|
? const Radius.circular(0)
|
|
: const Radius.circular(10))),
|
|
padding: const EdgeInsets.all(8),
|
|
child: message.content != null
|
|
? Column(
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment.end,
|
|
children: [
|
|
if (message.file != null)
|
|
message.file!.name.isVideo()
|
|
? GestureDetector(
|
|
onTap: () => DialogHandler(
|
|
context: context)
|
|
.showVideoHero(
|
|
url: message
|
|
.file!.path),
|
|
child: Container(
|
|
constraints:
|
|
const BoxConstraints(
|
|
maxWidth: double.infinity,
|
|
),
|
|
child: VideoThumbnailWidget(
|
|
videoUrl:
|
|
message.file!.path),
|
|
),
|
|
)
|
|
: message.file!.name.isImage()
|
|
? GestureDetector(
|
|
onTap: () =>
|
|
DialogHandler(
|
|
context:
|
|
context)
|
|
.showImageHero(
|
|
image: message
|
|
.file!
|
|
.path),
|
|
child: AspectRatio(
|
|
aspectRatio: 1 / 1,
|
|
child: Container(
|
|
constraints:
|
|
const BoxConstraints(
|
|
maxWidth:
|
|
double.infinity,
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
8),
|
|
child:
|
|
CustomeImage(
|
|
src: message
|
|
.file!.path,
|
|
fit: BoxFit
|
|
.cover,
|
|
)),
|
|
),
|
|
),
|
|
)
|
|
: message.file!.name.isAudio()
|
|
? Player(
|
|
fileUrl: message
|
|
.file!.path,
|
|
inMessages: true,
|
|
)
|
|
: Container(
|
|
decoration: BoxDecoration(
|
|
color: context
|
|
.read<
|
|
ThemeModeCubit>()
|
|
.isDark()
|
|
? AppColors
|
|
.black[
|
|
900]
|
|
: AppColors
|
|
.gray
|
|
.defaultShade,
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
10)),
|
|
padding:
|
|
const EdgeInsets
|
|
.all(8),
|
|
constraints:
|
|
const BoxConstraints(
|
|
minHeight:
|
|
64),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
child: message
|
|
.file!
|
|
.name
|
|
.isDocument()
|
|
? const Icon(
|
|
CupertinoIcons
|
|
.doc)
|
|
: const SizedBox
|
|
.shrink(),
|
|
),
|
|
Expanded(
|
|
child:
|
|
Padding(
|
|
padding: const EdgeInsets
|
|
.symmetric(
|
|
horizontal:
|
|
12.0),
|
|
child: Text(
|
|
message.file!
|
|
.name,
|
|
textDirection: message
|
|
.file!
|
|
.name
|
|
.startsWithEnglish()
|
|
? TextDirection
|
|
.ltr
|
|
: TextDirection
|
|
.rtl,
|
|
style: const TextStyle(
|
|
fontSize:
|
|
16),
|
|
overflow:
|
|
TextOverflow
|
|
.ellipsis,
|
|
maxLines: 2,
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
...List.generate(
|
|
message.content!.length,
|
|
(index) {
|
|
final content =
|
|
message.content![index];
|
|
return Column(
|
|
children: [
|
|
if (content.audioUrl != null)
|
|
Player(
|
|
fileUrl:
|
|
content.audioUrl!.url ??
|
|
'',
|
|
inMessages: true,
|
|
),
|
|
if (content.imageUrl != null)
|
|
Container(
|
|
constraints:
|
|
const BoxConstraints(
|
|
maxWidth: double.infinity,
|
|
),
|
|
child: AspectRatio(
|
|
aspectRatio: 1 / 1,
|
|
child: ImageNetwork(
|
|
url: content.imageUrl
|
|
?.url ??
|
|
'',
|
|
showHero: true,
|
|
radius: 10,
|
|
),
|
|
),
|
|
),
|
|
if (content.pdfUrl != null)
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: context
|
|
.read<
|
|
ThemeModeCubit>()
|
|
.isDark()
|
|
? AppColors
|
|
.black[900]
|
|
: AppColors.gray
|
|
.defaultShade,
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(10)),
|
|
padding:
|
|
const EdgeInsets.all(8),
|
|
constraints:
|
|
const BoxConstraints(
|
|
minHeight: 64),
|
|
child: Row(
|
|
children: [
|
|
const SizedBox(
|
|
child: Icon(
|
|
CupertinoIcons
|
|
.doc),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets
|
|
.symmetric(
|
|
horizontal:
|
|
12.0),
|
|
child: Text(
|
|
content.pdfUrl!.url
|
|
?.split('/')
|
|
.last ??
|
|
'',
|
|
textDirection: (content
|
|
.pdfUrl!
|
|
.url
|
|
?.split(
|
|
'/')
|
|
.last ??
|
|
'')
|
|
.startsWithEnglish()
|
|
? TextDirection
|
|
.ltr
|
|
: TextDirection
|
|
.rtl,
|
|
style:
|
|
const TextStyle(
|
|
fontSize:
|
|
16),
|
|
overflow:
|
|
TextOverflow
|
|
.ellipsis,
|
|
maxLines: 2,
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
if (content.audioUrl == null &&
|
|
content.imageUrl == null &&
|
|
content.pdfUrl == null)
|
|
Padding(
|
|
padding: const EdgeInsets
|
|
.symmetric(
|
|
horizontal: 8.0),
|
|
child: Builder(
|
|
builder: (context) {
|
|
directionName
|
|
.value = content
|
|
.text !=
|
|
null &&
|
|
content.text!
|
|
.startsWithEnglish()
|
|
? "LTR"
|
|
: 'RTL';
|
|
return DefaultMarkdownText(
|
|
key: markdownKey,
|
|
text: content.text ??
|
|
'',
|
|
fromBot:
|
|
message.fromBot!,
|
|
color: message
|
|
.fromBot!
|
|
? Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.onSurface
|
|
: Colors.white);
|
|
}),
|
|
)
|
|
],
|
|
);
|
|
},
|
|
),
|
|
if (message.fromBot!)
|
|
Padding(
|
|
padding: const EdgeInsets.only(
|
|
top: 8.0, left: 8.0),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment
|
|
.spaceBetween,
|
|
children: [
|
|
ValueListenableBuilder(
|
|
valueListenable:
|
|
directionName,
|
|
builder: (context, dir, _) {
|
|
return InkWell(
|
|
onTap: () {
|
|
directionName
|
|
.value = markdownKey
|
|
.currentState
|
|
?.changeDirection() ??
|
|
'RTL';
|
|
},
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets
|
|
.symmetric(
|
|
horizontal:
|
|
12,
|
|
vertical: 4),
|
|
decoration: BoxDecoration(
|
|
borderRadius:
|
|
BorderRadius
|
|
.circular(
|
|
4),
|
|
color: context
|
|
.read<
|
|
ThemeModeCubit>()
|
|
.isDark()
|
|
? AppColors
|
|
.black[
|
|
900]
|
|
: AppColors
|
|
.primaryColor[
|
|
50]),
|
|
child: Text(
|
|
dir,
|
|
style: TextStyle(
|
|
color: Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.primary),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
Row(
|
|
mainAxisSize:
|
|
MainAxisSize.min,
|
|
children: [
|
|
BlocProvider<
|
|
LikeMessageCubit>(
|
|
create: (context) =>
|
|
LikeMessageCubit()
|
|
..getLike(
|
|
like: message
|
|
.like),
|
|
child: BlocBuilder<
|
|
LikeMessageCubit,
|
|
LikeMessageState>(
|
|
builder:
|
|
(context, state) {
|
|
return DefaultPlaceHolder(
|
|
enabled: state
|
|
is LikeMessageLoading,
|
|
child: Row(
|
|
children: [
|
|
GestureDetector(
|
|
onTap:
|
|
() async {
|
|
await context.read<LikeMessageCubit>().setLike(
|
|
like: state
|
|
is LikeMessageLiked
|
|
? null
|
|
: true,
|
|
chatId:
|
|
chatId!,
|
|
messageId:
|
|
message.id!);
|
|
},
|
|
child:
|
|
Padding(
|
|
padding: const EdgeInsets
|
|
.only(
|
|
right:
|
|
20.0),
|
|
child:
|
|
SizedBox(
|
|
width: 16,
|
|
height:
|
|
16,
|
|
child: state
|
|
is LikeMessageLiked
|
|
? Assets
|
|
.icon
|
|
.bold
|
|
.like
|
|
.svg(color: AppColors.green.defaultShade)
|
|
: Assets.icon.outline.like.svg(color: Theme.of(context).colorScheme.primary),
|
|
),
|
|
),
|
|
),
|
|
GestureDetector(
|
|
onTap:
|
|
() async {
|
|
await context.read<LikeMessageCubit>().setLike(
|
|
like: state
|
|
is LikeMessageDisLiked
|
|
? null
|
|
: false,
|
|
chatId:
|
|
chatId!,
|
|
messageId:
|
|
message.id!);
|
|
},
|
|
child:
|
|
Padding(
|
|
padding: const EdgeInsets
|
|
.only(
|
|
right:
|
|
20.0),
|
|
child:
|
|
SizedBox(
|
|
width: 16,
|
|
height:
|
|
16,
|
|
child: state
|
|
is LikeMessageDisLiked
|
|
? Assets
|
|
.icon
|
|
.bold
|
|
.dislike
|
|
.svg(color: AppColors.red.defaultShade)
|
|
: Assets.icon.outline.dislike.svg(color: Theme.of(context).colorScheme.primary),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
if (widget.chatArgs.bot
|
|
.tool !=
|
|
null &&
|
|
!widget
|
|
.chatArgs.bot.tool!)
|
|
MorePopupMenuHandler(
|
|
context: context)
|
|
.morePopUpMenu(
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets
|
|
.only(
|
|
right:
|
|
20.0),
|
|
child:
|
|
CircleIconBtn(
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.magicpen,
|
|
color: context
|
|
.read<
|
|
ThemeModeCubit>()
|
|
.isDark()
|
|
? AppColors
|
|
.black[
|
|
900]
|
|
: AppColors
|
|
.primaryColor[50],
|
|
iconColor: Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.primary,
|
|
size: 28,
|
|
iconPadding:
|
|
const EdgeInsets
|
|
.all(
|
|
6),
|
|
),
|
|
),
|
|
items: [
|
|
PopUpMenuItemModel(
|
|
click: () {
|
|
try {
|
|
refreshQuestions
|
|
.value =
|
|
false;
|
|
sendRequest(
|
|
file: message
|
|
.file,
|
|
message:
|
|
'خلاصهتر بنویس');
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print(
|
|
'Error is: $e');
|
|
}
|
|
}
|
|
},
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 0,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.eraser,
|
|
title:
|
|
'خلاصهتر بنویس')),
|
|
),
|
|
PopUpMenuItemModel(
|
|
click: () {
|
|
try {
|
|
refreshQuestions
|
|
.value =
|
|
false;
|
|
sendRequest(
|
|
file: message
|
|
.file,
|
|
message:
|
|
'کامل بنویس');
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print(
|
|
'Error is: $e');
|
|
}
|
|
}
|
|
},
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 1,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.edit2,
|
|
title:
|
|
'کامل بنویس')),
|
|
),
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 2,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.voiceCricle,
|
|
title:
|
|
'لحن نوشته را تغییر بده')),
|
|
click: () async {
|
|
await BottomSheetHandler(
|
|
context)
|
|
.showStringList(
|
|
onSelect:
|
|
(value) {
|
|
try {
|
|
refreshQuestions.value =
|
|
false;
|
|
sendRequest(
|
|
file: message.file,
|
|
message: 'لحن نوشته را تغییر بده به $value');
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error is: $e');
|
|
}
|
|
}
|
|
},
|
|
title: 'انتخاب لحن نوشته',
|
|
values: [
|
|
'رسمی',
|
|
'عامیانه',
|
|
'دوستانه',
|
|
'حرفه ای',
|
|
'محاوره ای',
|
|
'طنز',
|
|
'جدی'
|
|
]);
|
|
},
|
|
),
|
|
PopUpMenuItemModel(
|
|
popupMenuItem: PopupMenuItem(
|
|
value: 3,
|
|
child: MorePopupMenuHandler.morePopUpItem(
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.translate,
|
|
title:
|
|
'ترجمه کن')),
|
|
click: () async {
|
|
await BottomSheetHandler(
|
|
context)
|
|
.showStringList(
|
|
onSelect:
|
|
(value) {
|
|
try {
|
|
refreshQuestions.value =
|
|
false;
|
|
sendRequest(
|
|
file: message.file,
|
|
message: 'زبان نوشته را تغییر بده به $value');
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error is: $e');
|
|
}
|
|
}
|
|
},
|
|
title:
|
|
'انتخاب زبان',
|
|
values: [
|
|
'🇮🇷 فارسی',
|
|
'Arabic 🇸🇦',
|
|
'Bengali 🇧🇩',
|
|
'English 🇬🇧',
|
|
'French 🇫🇷',
|
|
'German 🇩🇪',
|
|
'Hindi 🇮🇳',
|
|
'Italian 🇮🇹'
|
|
]);
|
|
},
|
|
),
|
|
]),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
)
|
|
: null,
|
|
),
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
message.error!
|
|
? CircleIconBtn(
|
|
key: containerKey,
|
|
icon: Assets.icon.outline.bitcoinRefresh,
|
|
iconColor: AppColors.red.defaultShade,
|
|
iconPadding: const EdgeInsets.all(6),
|
|
onTap: () async {
|
|
if (message.query != null) {
|
|
refreshQuestions.value = true;
|
|
sendRequest(
|
|
file: message.file,
|
|
withOutNewMessage: true,
|
|
message: message.query!,
|
|
);
|
|
context.read<MessagesBloc>().add(
|
|
ChangeMessage(
|
|
oldMessage: message,
|
|
newMessage: message.copyWith(
|
|
error: false)));
|
|
}
|
|
},
|
|
)
|
|
: Transform.rotate(
|
|
angle: pi / 2,
|
|
child: CircleIconBtn(
|
|
color: message.fromBot!
|
|
? context
|
|
.read<ThemeModeCubit>()
|
|
.isDark()
|
|
? AppColors.black[900]
|
|
: AppColors.primaryColor[50]
|
|
: AppColors.primaryColor.defaultShade,
|
|
iconColor: message.fromBot!
|
|
? Theme.of(context)
|
|
.colorScheme
|
|
.primary
|
|
: Colors.white,
|
|
key: containerKey,
|
|
icon: Assets.icon.outline.more,
|
|
iconPadding: const EdgeInsets.all(6),
|
|
onTap: () async {
|
|
_popUpMenu(message, containerKey);
|
|
},
|
|
),
|
|
)
|
|
],
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(4.0),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (message.fromBot!)
|
|
// Row(
|
|
// children: [
|
|
// ImageNetwork(
|
|
// url: bot.image ?? '',
|
|
// width: 16,
|
|
// height: 16,
|
|
// radius: 360,
|
|
// ),
|
|
// const SizedBox(
|
|
// width: 4,
|
|
// ),
|
|
// Text(bot.name ?? '',
|
|
// textDirection: TextDirection.rtl,
|
|
// style: AppTextStyles.body5.copyWith(
|
|
// fontWeight: FontWeight.bold,
|
|
// color: Theme.of(context)
|
|
// .colorScheme
|
|
// .onSurface)),
|
|
// const SizedBox(
|
|
// width: 8,
|
|
// ),
|
|
// ],
|
|
// ),
|
|
if (message.createdAt != null)
|
|
Text(
|
|
DateTimeUtils.convertToSentTime(
|
|
message.createdAt!),
|
|
style: AppTextStyles.body5.copyWith(
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onSurface)),
|
|
],
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
return chatScreen();
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget aNewMessage() {
|
|
return BlocConsumer<ReceiveMessageCubit, ReceiveMessageState>(
|
|
builder: (context, state) {
|
|
if (state is ReceiveMessageOnResponsing) {
|
|
return Container(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Container(
|
|
constraints: BoxConstraints(
|
|
minWidth: Responsive(context).isMobile()
|
|
? maxWidthDesktop * 0.4
|
|
: maxWidthDesktop * 0.2,
|
|
maxWidth: Responsive(context).isMobile()
|
|
? maxWidthDesktop * 0.8
|
|
: maxWidthDesktop * 0.6),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)
|
|
.copyWith(bottomLeft: const Radius.circular(0))),
|
|
padding: const EdgeInsets.all(16),
|
|
child: DefaultMarkdownText(
|
|
text: '${state.text}...',
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
if (state is ReceiveMessageLoading) {
|
|
return Container(
|
|
alignment: Alignment.centerLeft,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Container(
|
|
width: Responsive(context).isMobile()
|
|
? maxWidthDesktop * 0.8
|
|
: maxWidthDesktop * 0.6,
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)
|
|
.copyWith(bottomLeft: const Radius.circular(0))),
|
|
padding: const EdgeInsets.all(16),
|
|
child: Center(
|
|
child: SpinKitThreeBounce(
|
|
color: Theme.of(context).colorScheme.primary,
|
|
size: 32,
|
|
)),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
},
|
|
listener: (context, state) {
|
|
if (state is ReceiveMessageDone) {
|
|
context.read<MessagesBloc>().add(AddMessage(message: state.message));
|
|
if (state.model.chatId != null) {
|
|
if (chatId == null && !isGhost.value) {
|
|
context.read<ChatsHistoryBloc>().add(AddChat(
|
|
chats: Chats(
|
|
bot: bot,
|
|
title: state.model.chatTitle,
|
|
createdAt: DateTime.now().toIso8601String(),
|
|
id: state.model.chatId)));
|
|
}
|
|
chatId = state.model.chatId;
|
|
try {
|
|
if (widget.chatArgs.bot.attachment != 3 &&
|
|
(widget.chatArgs.bot.tool != null &&
|
|
!widget.chatArgs.bot.tool! &&
|
|
refreshQuestions.value)) {
|
|
context.read<RelatedQuestionsBloc>().add(GetAllRelatedQuestions(
|
|
chatId: chatId!,
|
|
messageId: state.message.id!,
|
|
content: state.message.query!));
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Error is: $e');
|
|
}
|
|
}
|
|
}
|
|
final humanMessage =
|
|
context.read<MessagesBloc>().state.messages.firstWhere(
|
|
(element) => element.id == state.oldHumanMessageId,
|
|
);
|
|
if (state.model.humanMessageId != null) {
|
|
context.read<MessagesBloc>().add(ChangeMessage(
|
|
oldMessage: humanMessage,
|
|
newMessage:
|
|
humanMessage.copyWith(id: state.model.humanMessageId)));
|
|
}
|
|
context.read<UserInfoCubit>().changeCredit(CreditModel(
|
|
credit: state.model.credit, freeCredit: state.model.freeCredit));
|
|
} else if (state is ReceiveMessageOnFail) {
|
|
SnackBarManager(context, id: 'ReceiveMessageOnFail').show(
|
|
status: SnackBarStatus.error,
|
|
message: state.detail,
|
|
);
|
|
final humanMessage =
|
|
context.read<MessagesBloc>().state.messages.firstWhere(
|
|
(element) => element.id == state.oldHumanMessageId,
|
|
);
|
|
context.read<MessagesBloc>().add(ChangeMessage(
|
|
oldMessage: humanMessage,
|
|
newMessage: humanMessage.copyWith(error: true)));
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget relatedQuestions() {
|
|
return BlocBuilder<RelatedQuestionsBloc, RelatedQuestionsState>(
|
|
builder: (context, state) {
|
|
if (state is RelatedQuestionsSuccess &&
|
|
state.relatedQuestionsModel.questions != null &&
|
|
state.relatedQuestionsModel.questions!.isNotEmpty) {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Assets.icon.outline.messageQuestion.svg(
|
|
color: Theme.of(context).colorScheme.primary),
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
Text(
|
|
'سوالات مرتبط:',
|
|
style: AppTextStyles.body3.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
color: Theme.of(context).colorScheme.primary),
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
const SizedBox(
|
|
height: 12,
|
|
),
|
|
ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: state.relatedQuestionsModel.questions!.length,
|
|
itemBuilder: (context, index) {
|
|
final question =
|
|
state.relatedQuestionsModel.questions![index];
|
|
return GestureDetector(
|
|
onTap: () {
|
|
refreshQuestions.value = true;
|
|
sendRequest(message: question);
|
|
},
|
|
child: Directionality(
|
|
textDirection: question.startsWithEnglish()
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16.0),
|
|
child: Text(
|
|
question,
|
|
textDirection: question.startsWithEnglish()
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
style: AppTextStyles.body4.copyWith(
|
|
color: AppColors.gray[context
|
|
.read<ThemeModeCubit>()
|
|
.isDark()
|
|
? 600
|
|
: 900]),
|
|
),
|
|
),
|
|
if (index !=
|
|
state.relatedQuestionsModel.questions!
|
|
.length -
|
|
1)
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.symmetric(vertical: 4.0),
|
|
child: Divider(
|
|
color: AppColors.gray.defaultShade,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
return const SizedBox();
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget chatScreen() {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: Column(
|
|
children: [
|
|
widget.chatArgs.isPerson
|
|
? Column(
|
|
children: [
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
ImageNetwork(
|
|
url: widget.chatArgs.bot.image ?? '',
|
|
width: 120,
|
|
height: 120,
|
|
radius: 16,
|
|
),
|
|
const SizedBox(
|
|
height: 16,
|
|
),
|
|
Text(
|
|
widget.chatArgs.bot.name ?? '',
|
|
style: AppTextStyles.headline6.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
if (widget.chatArgs.bot.description != null)
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
margin: const EdgeInsets.symmetric(vertical: 16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
color: Theme.of(context).colorScheme.surface),
|
|
child: Center(
|
|
child: Text(
|
|
widget.chatArgs.bot.description!,
|
|
textDirection: TextDirection.rtl,
|
|
textAlign: TextAlign.justify,
|
|
style: AppTextStyles.body5.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: bot.tool ?? false
|
|
? Column(
|
|
children: [
|
|
const SizedBox(
|
|
height: 24,
|
|
),
|
|
Center(
|
|
child: ImageNetwork(
|
|
url: bot.image ?? '',
|
|
width: 64,
|
|
height: 64,
|
|
radius: 360,
|
|
color:
|
|
bot.image != null && bot.image!.contains('/llm')
|
|
? Theme.of(context).colorScheme.onSurface
|
|
: null,
|
|
),
|
|
),
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
Text(
|
|
bot.name ?? '',
|
|
textDirection: TextDirection.rtl,
|
|
style: AppTextStyles.headline6.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
if (bot.description != null)
|
|
LayoutBuilder(builder: (context, constraints) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: maxLines,
|
|
builder: (context, lines, _) {
|
|
final span = TextSpan(
|
|
text: bot.description,
|
|
style: AppTextStyles.body4.copyWith(
|
|
color: AppColors.gray[context
|
|
.read<ThemeModeCubit>()
|
|
.isDark()
|
|
? 600
|
|
: 900]));
|
|
final tp = TextPainter(
|
|
text: span,
|
|
textDirection: TextDirection.ltr);
|
|
tp.layout(maxWidth: constraints.maxWidth);
|
|
final numLines =
|
|
tp.computeLineMetrics().length;
|
|
return Padding(
|
|
padding:
|
|
const EdgeInsets.fromLTRB(4, 12, 4, 4),
|
|
child: Stack(
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Text(
|
|
bot.description!,
|
|
textDirection: TextDirection.rtl,
|
|
style: AppTextStyles.body4.copyWith(
|
|
color: AppColors.gray[context
|
|
.read<
|
|
ThemeModeCubit>()
|
|
.isDark()
|
|
? 600
|
|
: 900]),
|
|
maxLines: (lines),
|
|
textAlign: TextAlign.justify,
|
|
),
|
|
if (lines == null && numLines >= 5)
|
|
Transform.rotate(
|
|
angle: -pi / 2,
|
|
child: CircleIconBtn(
|
|
onTap: () {
|
|
if (maxLines.value ==
|
|
null) {
|
|
maxLines.value = 5;
|
|
return;
|
|
}
|
|
maxLines.value = null;
|
|
},
|
|
size: 46,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary,
|
|
iconColor: Colors.white,
|
|
icon: Assets.icon.outline
|
|
.arrowRight)),
|
|
],
|
|
),
|
|
if (lines != null && numLines > lines)
|
|
Positioned(
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
child: Container(
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
Theme.of(context)
|
|
.scaffoldBackgroundColor,
|
|
Theme.of(context)
|
|
.scaffoldBackgroundColor
|
|
.withAlpha(140)
|
|
],
|
|
begin:
|
|
Alignment.bottomCenter,
|
|
end: Alignment.topCenter,
|
|
),
|
|
),
|
|
alignment:
|
|
Alignment.bottomCenter,
|
|
child: SizedBox(
|
|
child: Transform.rotate(
|
|
angle: pi / 2,
|
|
child: CircleIconBtn(
|
|
onTap: () {
|
|
if (maxLines
|
|
.value ==
|
|
null) {
|
|
maxLines.value =
|
|
5;
|
|
return;
|
|
}
|
|
maxLines.value =
|
|
null;
|
|
},
|
|
size: 46,
|
|
color:
|
|
Theme.of(context)
|
|
.colorScheme
|
|
.primary,
|
|
iconColor:
|
|
Colors.white,
|
|
icon: Assets
|
|
.icon
|
|
.outline
|
|
.arrowRight)),
|
|
),
|
|
))
|
|
],
|
|
),
|
|
);
|
|
});
|
|
}),
|
|
const SizedBox(
|
|
height: 8,
|
|
),
|
|
],
|
|
)
|
|
: Container(
|
|
padding: const EdgeInsets.all(12),
|
|
margin: const EdgeInsets.symmetric(vertical: 16),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8),
|
|
color: Theme.of(context).colorScheme.surface),
|
|
child: Center(
|
|
child: Text(
|
|
'سلام! به هوشان خوش اومدی. من اینجا هستم تا پاسخگوی سوالاتت باشم. امیدوارم در استفاده از هوشان تجربه خوبی داشته باشی!',
|
|
textDirection: TextDirection.rtl,
|
|
textAlign: TextAlign.justify,
|
|
style: AppTextStyles.body5.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
),
|
|
),
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
ValueListenableBuilder(
|
|
valueListenable: isGhost,
|
|
builder: (context, g, _) {
|
|
return Transform.scale(
|
|
scale: 0.8,
|
|
child: Switch.adaptive(
|
|
value: g,
|
|
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
|
|
(Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return Icon(CustomIcons.ghost,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onSurface);
|
|
}
|
|
return Icon(Icons.close,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.onSurface);
|
|
},
|
|
),
|
|
onChanged: (value) {
|
|
isGhost.value = value;
|
|
},
|
|
),
|
|
);
|
|
}),
|
|
const SizedBox(
|
|
width: 8,
|
|
),
|
|
Text(
|
|
'حالت ناشناس',
|
|
style: AppTextStyles.body4.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(
|
|
width: 8,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.only(bottom: 4.0),
|
|
child: HintTooltip(
|
|
hint:
|
|
'با فعال کردن این گزینه؛ چتهای شما در قسمت تاریخچه، ذخیره نمیشوند و اطلاعاتتان ناشناس باقی میماند.',
|
|
iconColor: Theme.of(context).colorScheme.onSurface,
|
|
),
|
|
)
|
|
],
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.secondary,
|
|
borderRadius: BorderRadius.circular(12)),
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
bot.cost == 0 || bot.cost == null
|
|
? 'رایگان'
|
|
: bot.cost.toString(),
|
|
style: AppTextStyles.body3.copyWith(color: Colors.white),
|
|
),
|
|
const SizedBox(
|
|
width: 4,
|
|
),
|
|
Assets.icon.outline.coin
|
|
.svg(color: Colors.white, width: 18, height: 18)
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget messageBar() {
|
|
return Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ValueListenableBuilder(
|
|
valueListenable: selectedFile,
|
|
builder: (context, value, child) {
|
|
if (value != null && !showRecorder.value) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: BorderRadius.circular(10)),
|
|
margin: const EdgeInsets.all(16),
|
|
padding: const EdgeInsets.all(8),
|
|
constraints: const BoxConstraints(minHeight: 64),
|
|
child: Row(
|
|
children: [
|
|
SizedBox(
|
|
child: value.name.isImage()
|
|
? GestureDetector(
|
|
onTap: () => DialogHandler(context: context)
|
|
.showImageHero(image: value.path),
|
|
child: SizedBox(
|
|
width: 46,
|
|
child: AspectRatio(
|
|
aspectRatio: 3 / 4,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(8),
|
|
child: CustomeImage(
|
|
src: value.path,
|
|
fit: BoxFit.cover,
|
|
)),
|
|
),
|
|
),
|
|
)
|
|
: value.path.isDocument()
|
|
? const Icon(CupertinoIcons.doc)
|
|
: value.path.isAudio()
|
|
? Player(
|
|
fileUrl: value.path,
|
|
inMessages: true,
|
|
)
|
|
: const SizedBox.shrink(),
|
|
),
|
|
Expanded(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
|
child: Text(
|
|
value.name,
|
|
textDirection: value.name.startsWithEnglish()
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
overflow: TextOverflow.ellipsis,
|
|
maxLines: 2,
|
|
),
|
|
)),
|
|
CircleIconBtn(
|
|
icon: Assets.icon.outline.trash,
|
|
color: AppColors.red[50],
|
|
iconColor: AppColors.red.defaultShade,
|
|
iconPadding: const EdgeInsets.all(6),
|
|
onTap: () {
|
|
selectedFile.value = null;
|
|
},
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
|
margin: Responsive(context).isMobile()
|
|
? EdgeInsets.zero
|
|
: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surface,
|
|
borderRadius: const BorderRadius.only(
|
|
topLeft: Radius.circular(32), topRight: Radius.circular(32)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.1),
|
|
offset: const Offset(0, 0),
|
|
blurRadius: 12,
|
|
spreadRadius: 0,
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
ValueListenableBuilder(
|
|
valueListenable: showRecorder,
|
|
builder: (context, inRecord, child) {
|
|
return inRecord
|
|
? Recorder(
|
|
play: true,
|
|
onDelete: () {
|
|
selectedFile.value = null;
|
|
showRecorder.value = false;
|
|
},
|
|
onError: (e) {
|
|
showRecorder.value = false;
|
|
recording.value = false;
|
|
},
|
|
onRecordFinish: (file) {
|
|
selectedFile.value = file;
|
|
recording.value = false;
|
|
},
|
|
)
|
|
: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
Expanded(
|
|
child: widget.chatArgs.bot.attachment == 3
|
|
? const SizedBox.shrink()
|
|
: SizedBox(
|
|
child: ValueListenableBuilder(
|
|
valueListenable: selectedFile,
|
|
builder: (context, file, child) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: messageText,
|
|
builder:
|
|
(context, text, child) {
|
|
return Directionality(
|
|
textDirection: text.text
|
|
.startsWithEnglish()
|
|
? TextDirection.ltr
|
|
: TextDirection.rtl,
|
|
child: TextField(
|
|
controller: messageText,
|
|
onChanged: (value) {},
|
|
|
|
enabled: (bot.deleted !=
|
|
null &&
|
|
!bot
|
|
.deleted!) &&
|
|
(context
|
|
.watch<
|
|
ReceiveMessageCubit>()
|
|
.state
|
|
is! ReceiveMessageOnResponsing &&
|
|
context
|
|
.watch<
|
|
ReceiveMessageCubit>()
|
|
.state
|
|
is! ReceiveMessageLoading) &&
|
|
!(bot.attachment ==
|
|
1 &&
|
|
file != null) &&
|
|
(widget.chatArgs.bot
|
|
.attachment !=
|
|
3),
|
|
minLines: 1,
|
|
maxLines: 6, // Set this
|
|
keyboardType:
|
|
TextInputType
|
|
.multiline,
|
|
style: AppTextStyles
|
|
.body4
|
|
.copyWith(
|
|
color: Theme.of(
|
|
context)
|
|
.colorScheme
|
|
.onSurface),
|
|
decoration:
|
|
InputDecoration(
|
|
contentPadding:
|
|
const EdgeInsets
|
|
.fromLTRB(
|
|
0, 12, 0, 12),
|
|
filled: true,
|
|
hintText: (bot.deleted !=
|
|
null &&
|
|
bot.deleted!)
|
|
? 'دستیار مورد نظر توسط سازنده حذف شده است!'
|
|
: 'چیزی بنویسید ...',
|
|
hintStyle:
|
|
AppTextStyles
|
|
.body4,
|
|
fillColor: Colors
|
|
.transparent,
|
|
border:
|
|
const OutlineInputBorder(
|
|
borderSide:
|
|
BorderSide.none,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(
|
|
width: 16,
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
if ((bot.deleted != null && !bot.deleted!))
|
|
ValueListenableBuilder(
|
|
valueListenable: selectedFile,
|
|
builder: (context, file, child) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: recording,
|
|
builder: (context, inRecording, child) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: showRecorder,
|
|
builder:
|
|
(context, showRecord, child) {
|
|
return ValueListenableBuilder(
|
|
valueListenable: messageText,
|
|
builder: (context, message,
|
|
child) {
|
|
return inRecording
|
|
? const SizedBox
|
|
.shrink()
|
|
: bot.attachmentType !=
|
|
null &&
|
|
bot.attachmentType!
|
|
.contains(
|
|
'audio') &&
|
|
message.text
|
|
.replaceAll(
|
|
' ', '')
|
|
.isEmpty &&
|
|
!showRecord &&
|
|
file == null
|
|
? GestureDetector(
|
|
onTap: () {
|
|
showRecorder
|
|
.value =
|
|
true;
|
|
recording
|
|
.value =
|
|
true;
|
|
},
|
|
child: Assets
|
|
.icon
|
|
.outline
|
|
.microphoneChat
|
|
.svg(
|
|
width:
|
|
24,
|
|
height:
|
|
24,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary))
|
|
: GestureDetector(
|
|
onTap: () {
|
|
if ((messageText
|
|
.text
|
|
.replaceAll(' ',
|
|
'')
|
|
.isEmpty &&
|
|
widget.chatArgs.bot.attachment !=
|
|
3) &&
|
|
selectedFile
|
|
.value ==
|
|
null) {
|
|
return;
|
|
}
|
|
if (selectedFile
|
|
.value !=
|
|
null &&
|
|
bot.attachment ==
|
|
1) {
|
|
return;
|
|
}
|
|
refreshQuestions
|
|
.value =
|
|
true;
|
|
sendRequest(
|
|
file: selectedFile
|
|
.value,
|
|
message:
|
|
messageText
|
|
.text);
|
|
},
|
|
child: Assets
|
|
.icon
|
|
.bold
|
|
.send
|
|
.svg(
|
|
width:
|
|
24,
|
|
height: 24));
|
|
},
|
|
);
|
|
});
|
|
});
|
|
}),
|
|
const SizedBox(
|
|
width: 8,
|
|
),
|
|
// CircleIconBtn(
|
|
// icon: Assets.icon.outline.infoCircle,
|
|
// color: Theme.of(context).colorScheme.surface,
|
|
// iconColor: Theme.of(context).colorScheme.onSurface,
|
|
// onTap: () {
|
|
// showInfo.value = !showInfo.value;
|
|
// },
|
|
// ),
|
|
],
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
ValueListenableBuilder(
|
|
valueListenable: webSearch,
|
|
builder: (context, isWebSearchEnabled, _) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
webSearch.value = !webSearch.value;
|
|
},
|
|
child: HintTooltip(
|
|
hint: 'جستجو در وب',
|
|
child: Assets.icon.outline.globalSearch.svg(
|
|
width: 24,
|
|
height: 24,
|
|
color: isWebSearchEnabled
|
|
? Theme.of(context)
|
|
.colorScheme
|
|
.primary
|
|
: Theme.of(context)
|
|
.colorScheme
|
|
.onSurface
|
|
.withOpacity(0.4),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
// ValueListenableBuilder(
|
|
// valueListenable: webSearch,
|
|
// builder: (context, canWebSearch, _) {
|
|
// return ChoiceChip(
|
|
// padding: EdgeInsets.zero,
|
|
// showCheckmark: false,
|
|
// labelPadding:
|
|
// const EdgeInsets.symmetric(horizontal: 8),
|
|
// selectedColor: Theme.of(context)
|
|
// .colorScheme
|
|
// .primary, // Change selected color
|
|
// backgroundColor:
|
|
// Theme.of(context).colorScheme.surface,
|
|
// surfaceTintColor: Colors.transparent,
|
|
// selectedShadowColor: Colors.transparent,
|
|
// shape: RoundedRectangleBorder(
|
|
// borderRadius: BorderRadius.circular(360)),
|
|
// onSelected: (value) {
|
|
// webSearch.value = value;
|
|
// },
|
|
// label: Row(
|
|
// children: [
|
|
// Assets.icon.outline.globalSearch.svg(
|
|
// color: canWebSearch
|
|
// ? Colors.white
|
|
// : Theme.of(context)
|
|
// .colorScheme
|
|
// .onSurface),
|
|
// const SizedBox(
|
|
// width: 4,
|
|
// ),
|
|
// Text(
|
|
// 'جستجو در وب',
|
|
// style: AppTextStyles.body5.copyWith(
|
|
// color: canWebSearch
|
|
// ? Colors.white
|
|
// : Theme.of(context)
|
|
// .colorScheme
|
|
// .onSurface),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// selected: canWebSearch);
|
|
// }),
|
|
const SizedBox(
|
|
width: 12,
|
|
),
|
|
if ((bot.deleted != null && !bot.deleted!))
|
|
if (bot.attachmentType != null &&
|
|
bot.attachmentType!.isNotEmpty)
|
|
ValueListenableBuilder(
|
|
valueListenable: visibleAttach,
|
|
builder: (context, value, child) {
|
|
return AnimatedVisibility(
|
|
isVisible: value,
|
|
duration:
|
|
const Duration(milliseconds: 300),
|
|
fadeMode: FadeMode.horizontal,
|
|
child: Padding(
|
|
padding:
|
|
const EdgeInsets.only(left: 12.0),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.end,
|
|
children: [
|
|
if (bot.attachmentType!
|
|
.contains('image'))
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.only(
|
|
right: 8.0),
|
|
child: CircleIconBtn(
|
|
icon: Assets.icon.outline
|
|
.galleryAdd,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary,
|
|
iconColor: Colors.white,
|
|
onTap: () async {
|
|
await BottomSheetHandler(
|
|
context)
|
|
.showPickImage(
|
|
onSelect: (file) {
|
|
selectedFile.value =
|
|
file;
|
|
if (widget
|
|
.chatArgs
|
|
.bot
|
|
.attachment !=
|
|
3) {
|
|
visibleAttach
|
|
.value =
|
|
false;
|
|
}
|
|
},
|
|
);
|
|
}),
|
|
),
|
|
if (bot.attachmentType!
|
|
.contains('audio'))
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.only(
|
|
right: 8.0),
|
|
child: CircleIconBtn(
|
|
icon: Assets
|
|
.icon.outline.musicnote,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary,
|
|
iconColor: Colors.white,
|
|
onTap: () async {
|
|
final file =
|
|
await PickFileService(
|
|
context)
|
|
.getFile(
|
|
fileType:
|
|
FileType
|
|
.audio);
|
|
if (file != null) {
|
|
selectedFile.value =
|
|
file.single;
|
|
if (widget.chatArgs.bot
|
|
.attachment !=
|
|
3) {
|
|
visibleAttach.value =
|
|
false;
|
|
}
|
|
}
|
|
},
|
|
),
|
|
),
|
|
if (bot.attachmentType!
|
|
.contains('pdf'))
|
|
Padding(
|
|
padding:
|
|
const EdgeInsets.only(
|
|
right: 8.0),
|
|
child: CircleIconBtn(
|
|
icon: Assets
|
|
.icon.outline.cardAdd,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary,
|
|
iconColor: Colors.white,
|
|
onTap: () async {
|
|
final file =
|
|
await PickFileService(
|
|
context)
|
|
.getFile(
|
|
fileType:
|
|
FileType
|
|
.custom,
|
|
allowedExtensions: [
|
|
'pdf',
|
|
'doc',
|
|
'docx',
|
|
'xls',
|
|
'xlsx',
|
|
'xlsm',
|
|
'xlsb',
|
|
'xlt',
|
|
'xltx',
|
|
'xltm'
|
|
]);
|
|
if (file != null) {
|
|
selectedFile.value =
|
|
file.single;
|
|
if (widget
|
|
.chatArgs
|
|
.bot
|
|
.attachment !=
|
|
3) {
|
|
visibleAttach
|
|
.value = false;
|
|
}
|
|
}
|
|
}),
|
|
)
|
|
],
|
|
),
|
|
));
|
|
},
|
|
),
|
|
if ((bot.deleted != null && !bot.deleted!))
|
|
if (bot.attachment != 0 &&
|
|
bot.attachmentType != null &&
|
|
bot.attachmentType!.isNotEmpty &&
|
|
bot.attachment != 3)
|
|
GestureDetector(
|
|
onTap: () {
|
|
if (widget.chatArgs.bot.attachment != 3) {
|
|
visibleAttach.value =
|
|
!visibleAttach.value;
|
|
}
|
|
},
|
|
child: Assets.icon.outline.elementPlus.svg(
|
|
width: 24,
|
|
height: 24,
|
|
color: Theme.of(context)
|
|
.colorScheme
|
|
.primary)),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 10,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(
|
|
height: 4,
|
|
),
|
|
ValueListenableBuilder(
|
|
valueListenable: showInfo,
|
|
builder: (context, show, _) {
|
|
if (show) {
|
|
return Text(
|
|
'مدلهای هوش مصنوعی میتوانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
|
|
style: AppTextStyles.body6.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurface),
|
|
);
|
|
}
|
|
return const SizedBox.shrink();
|
|
})
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|