// ignore_for_file: deprecated_member_use import 'dart:async'; import 'dart:io'; import 'package:animated_custom_dropdown/custom_dropdown.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/ai/bot_assistants_req_model.dart'; import 'package:didvan/models/ai/bots_model.dart'; import 'package:didvan/models/ai/file_create_assistants_model.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/extension.dart'; import 'package:didvan/views/ai/bot_assistants_state.dart'; import 'package:didvan/views/ai/create_bot_assistants_state.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/switch.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text_field.dart'; import 'package:didvan/views/widgets/hoshan_app_bar.dart'; import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; class CreateBotAssistantsPage extends StatefulWidget { final int? id; const CreateBotAssistantsPage({Key? key, this.id}) : super(key: key); @override State createState() => _CreateBotAssistantsPageState(); } class _CreateBotAssistantsPageState extends State { late InputBorder defaultBorder = OutlineInputBorder( borderRadius: DesignConfig.mediumBorderRadius, borderSide: BorderSide( width: 1, color: Theme.of(context).colorScheme.disabledText)); // final _formYouTubeKey = GlobalKey(); final _formNameKey = GlobalKey(); final _formPromptKey = GlobalKey(); final _formDescKey = GlobalKey(); Timer? _timer; @override void initState() { super.initState(); } void onConfirm(CreateBotAssistantsState state) async { bool isValid = true; // if (!_formYouTubeKey.currentState!.validate()) { // isValid = false; // } if (!_formNameKey.currentState!.validate()) { isValid = false; } if (!_formDescKey.currentState!.validate()) { isValid = false; } if (!_formPromptKey.currentState!.validate()) { isValid = false; } if (!isValid) { return; } final List resultFiles = []; for (var file in state.files) { if (!file.fromNetwork) { resultFiles.add(file.file!); } } final success = await state.createAssistants( id: widget.id, data: BotAssistantsReqModel( type: state.selectedBotType, name: state.name, description: state.desc, botId: state.initialBot!.id!, prompt: state.prompt, webLinks: state.countOfLink.first.isEmpty ? null : state.countOfLink, // youtubeLink: youtubeLink, isPrivate: state.isPrivate, files: resultFiles, deleteImage: state.assistant != null ? state.assistant!.image == null : state.image.value == null, image: state.image.value)); if (success) { context.read().getMyAssissmant(); context.read().assistant = null; Navigator.pop(context); } else { ActionSheetUtils(context).showAlert(AlertData( message: 'مشکلی در ارتباط با سرور پیش آمده دوباره تلاش کنید', aLertType: ALertType.error)); } } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { context.read().assistant = null; return true; }, child: Scaffold( appBar: HoshanAppBar( onBack: () { context.read().assistant = null; Navigator.pop(context); }, withActions: false, ), body: Consumer(builder: (BuildContext context, CreateBotAssistantsState state, Widget? child) { return state.loading ? Center( child: SpinKitThreeBounce( size: 46, color: Theme.of(context).colorScheme.primary, ), ) : SingleChildScrollView( physics: const BouncingScrollPhysics(), child: Padding( padding: const EdgeInsets.symmetric( horizontal: 20.0, vertical: 32), child: Column( children: [ title(text: 'نوع دستیار'), SizedBox( width: MediaQuery.sizeOf(context).width, child: CustomDropdown( closedHeaderPadding: const EdgeInsets.all(12), items: state.botModels, enabled: state.assistant == null, initialItem: state.selectedBotType == 'text' ? state.botModels.first : state.botModels.last, hideSelectedFieldWhenExpanded: false, decoration: CustomDropdownDecoration( listItemDecoration: ListItemDecoration( selectedColor: Theme.of(context) .colorScheme .surface .withOpacity(0.5), ), closedBorder: Border.all(color: Colors.grey), closedFillColor: Theme.of(context).colorScheme.surface, expandedFillColor: Theme.of(context).colorScheme.surface), // hintText: "انتخاب کنید", onChanged: (value) { final index = state.botModels.indexOf(value!); state.selectedBotType = index == 0 ? 'text' : 'image'; state.update(); }, ), ), const SizedBox( height: 24, ), ValueListenableBuilder( valueListenable: state.image, builder: (context, img, _) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ DidvanButton( width: 120, style: ButtonStyleMode.flat, color: state.image.value != null || state.assistant?.image != null ? Theme.of(context).colorScheme.error : null, title: state.image.value != null || state.assistant?.image != null ? 'حذف عکس' : 'انتخاب عکس', onPressed: () async { if (state.image.value != null || state.assistant?.image != null) { if (state.assistant != null) { state.assistant!.image = null; } state.image.value = null; return; } final pickedFile = await MediaService.pickImage( source: ImageSource.gallery); File? file; if (pickedFile != null && !kIsWeb) { file = await ImageCropper().cropImage( sourcePath: pickedFile.path, androidUiSettings: const AndroidUiSettings( toolbarTitle: 'برش تصویر'), iosUiSettings: const IOSUiSettings( title: 'برش تصویر', doneButtonTitle: 'تایید', cancelButtonTitle: 'بازگشت', ), compressQuality: 30, ); if (file != null) { state.image.value = XFile(file.path); } } }, ), img != null ? ClipRRect( borderRadius: DesignConfig.lowBorderRadius, child: SizedBox( width: 80, height: 80, child: Image.file(File(img.path))), ) : SkeletonImage( imageUrl: state.assistant != null && state.assistant!.image != null ? state.assistant!.image! : 'https://via.placeholder.com/70x70', width: 80, height: 80, ) ], ); }), const SizedBox( height: 24, ), title(text: 'انتخاب نام'), Form( key: _formNameKey, child: DidvanTextField( initialValue: state.name, onChanged: (value) { state.name = value; if (value.isEmpty) { return; } if (state.assistant == null) { _timer?.cancel(); _timer = Timer(const Duration(seconds: 1), () async { await state.getAssistantsName(name: value); _formNameKey.currentState!.validate(); }); } }, validator: (value) { String? result; if (value.isEmpty) { result = 'نام نباید خالی باشد'; } else if (value.length < 4) { result = 'نام نباید کمتر از 4 حرف باشد'; } else if (state.assistant == null && !state.successName) { result = 'اسم دیگری انتخاب کنید این اسم موجود است'; } return result; }, hintText: 'ai@2024_B', maxLength: 20, ), ), const SizedBox( height: 8, ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ state.loadingName ? const SizedBox( width: 18, height: 18, child: CircularProgressIndicator()) : Icon( DidvanIcons.info_circle_light, color: Theme.of(context).colorScheme.caption, ), const SizedBox(width: 4), Expanded( child: DidvanText( state.loadingName ? '...درحال بررسی اسم' : 'نام منحصر به فرد شامل 4 تا 20 کاراکتر (حروف، اعداد، خط تیره، نقطه و زیرخط) ', textAlign: TextAlign.right, fontSize: 12, color: Theme.of(context).colorScheme.caption, ), ), ], ), const SizedBox( height: 24, ), title(text: 'توضیحات بات'), Form( key: _formDescKey, child: DidvanTextField( initialValue: state.desc, hintText: 'توضیح دهید چه کارهایی از این ربات بر می‌آید.', textInputType: TextInputType.multiline, minLine: 4, maxLine: 4, maxLength: 200, hasHeight: false, showLen: true, onChanged: (value) { state.desc = value; }, validator: (value) { String? result; if (value.isEmpty) { result = 'توضیحات نباید خالی باشد'; } else if (value.length < 10) { result = 'توضیحات نباید کمتر از 10 حرف باشد'; } return result; }, ), ), const SizedBox( height: 24, ), title(text: 'نوع دستیار'), state.loadingImageBots ? ShimmerPlaceholder( width: MediaQuery.sizeOf(context).width, height: 48, borderRadius: DesignConfig.lowBorderRadius, ) : SizedBox( width: MediaQuery.sizeOf(context).width, child: CustomDropdown( closedHeaderPadding: const EdgeInsets.all(12), items: state.selectedBotType == 'text' ? state.allBots : state.imageBots, headerBuilder: (context, bot, enabled) => botRow(bot, context), listItemBuilder: (context, bot, isSelected, onItemSelect) => botRow(bot, context), initialItem: state.assistant != null ? state.initialBot : (state.selectedBotType == 'text' ? state.allBots : state.imageBots) .first, hideSelectedFieldWhenExpanded: false, decoration: CustomDropdownDecoration( listItemDecoration: ListItemDecoration( selectedColor: Theme.of(context) .colorScheme .surface .withOpacity(0.5), ), closedBorder: Border.all(color: Colors.grey), expandedFillColor: Theme.of(context) .colorScheme .surface), // hintText: "انتخاب کنید", onChanged: (value) { state.initialBot = value; }, ), ), const SizedBox( height: 24, ), title(text: 'دستورالعمل'), Form( key: _formPromptKey, child: DidvanTextField( initialValue: state.prompt, hintText: 'به ربات خود بگویید که چگونه رفتار کند و چگونه به پیام‌های کاربر پاسخ دهد. سعی کنید تا حد امکان پیام واضح و مشخص باشد.', textInputType: TextInputType.multiline, minLine: 6, maxLine: 6, maxLength: 400, hasHeight: false, showLen: true, onChanged: (value) { state.prompt = value; }, validator: (value) { String? result; if (value.isEmpty) { result = 'دستورالعمل نباید خالی باشد'; } else if (value.length < 10) { result = 'نام نباید کمتر از 10 حرف باشد'; } return result; }, ), ), const SizedBox( height: 24, ), if (state.assistant == null) Column( children: [ if (state.selectedBotType == 'text') Column( children: [ title( text: 'پایگاه دانش', isRequired: false), if (state.files.length != 3) SizedBox( height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context) .colorScheme .disabledBackground, shape: const RoundedRectangleBorder( borderRadius: DesignConfig .lowBorderRadius)), onPressed: () async { final picks = await MediaService .pickMultiFile(); if (picks != null) { for (var file in picks.xFiles) { if (file.path.isDocument() || file.path.isAudio()) { state.files.add( FileCreateAssistantsModel( fromNetwork: false, file: file, url: null)); } else { ActionSheetUtils(context) .showAlert(AlertData( message: 'باید فایل انتخاب شده صوتی یا Pdf باشد', aLertType: ALertType .error)); } } } state.update(); }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( CupertinoIcons.add, color: Theme.of(context) .colorScheme .caption, ), const SizedBox( width: 4, ), DidvanText( 'آپلود فایل (فایل صوتی، پی دی اف)', color: Theme.of(context) .colorScheme .caption, fontSize: 16, ) ], )), ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ...List.generate( state.files.length, (index) { return SizedBox( child: Stack( children: [ Container( width: MediaQuery .sizeOf( context) .width / 5, height: MediaQuery.sizeOf( context) .width / 5, margin: const EdgeInsets .symmetric( horizontal: 8, vertical: 12), padding: const EdgeInsets.all( 8), decoration: BoxDecoration( color: Theme.of(context) .colorScheme .disabledBackground, borderRadius: DesignConfig .lowBorderRadius), child: Column( children: [ Expanded( child: state .files[ index] .fromNetwork ? state .files[ index] .url! .isImage() ? CachedNetworkImage( imageUrl: state .files[ index] .url!) : const Icon( CupertinoIcons .doc) : state .files[ index] .file! .path .isImage() ? Image.file(File(state .files[ index] .file! .path)) : const Icon( CupertinoIcons .doc), ), MarqueeText( text: state .files[ index] .fromNetwork ? state .files[ index] .url! .split( '/') .last : state .files[ index] .file! .name, textDirection: TextDirection .rtl, style: Theme.of( context) .textTheme .labelSmall!) ], )), Positioned( top: 8, left: 4, child: InkWell( onTap: () { state.files .removeAt(index); state.update(); }, child: Container( padding: const EdgeInsets .all(6), decoration: BoxDecoration( shape: BoxShape .circle, color: Theme.of( context) .colorScheme .error), child: const Icon( DidvanIcons .trash_solid, color: Colors.white, size: 18, ), ), )) ], ), ); }, ) ], ), const SizedBox( height: 24, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ title( text: 'لینک وب سایت', isRequired: false), Row( children: [ if (state.countOfLink.length > 1) DidvanIconButton( icon: CupertinoIcons .minus_circle_fill, onPressed: () { state.countOfLink .removeLast(); state.update(); }, ), if (state.countOfLink.length != 3) DidvanIconButton( icon: CupertinoIcons .plus_circle_fill, onPressed: () { if (state.countOfLink.last .isNotEmpty) { state.countOfLink.add(''); state.update(); } }, ), ], ) ], ), ListView.builder( shrinkWrap: true, itemCount: state.countOfLink.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) => Column( children: [ DidvanTextField( onChanged: (value) { state.countOfLink[index] = value; if (state.countOfLink[index] .isEmpty && state.countOfLink.length != 1) { state.countOfLink .removeAt(index); } state.update(); }, // validator: (value) {}, hintText: 'https://www.weforum.org/agenda/2024/08', ), const SizedBox( height: 8, ), ], ), ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon( DidvanIcons.info_circle_light, color: Theme.of(context) .colorScheme .caption, ), const SizedBox(width: 4), Expanded( child: DidvanText( 'دستیار شما با استناد بر اطلاعات ارائه شده در پایگاه دانش، پیام کاربران را ارزیابی می‌کند.', textAlign: TextAlign.right, fontSize: 12, color: Theme.of(context) .colorScheme .caption, ), ), ], ), const SizedBox( height: 24, ), ], ), // title(text: 'لینک یوتیوب', isRequired: false), // Form( // key: _formYouTubeKey, // child: DidvanTextField( // onChanged: (value) { // youtubeLink = value; // }, // validator: (value) => // value.startsWith('https://www.youtube.com') || // value.isEmpty // ? null // : 'باید لینک یوتیوب باشد', // hintText: 'https://www.youtube.com/watch?v', // ), // ), // const SizedBox( // height: 24, // ), ], ), Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const DidvanText( 'نمایش عمومی', fontSize: 14, ), Row( children: [ Expanded( child: DidvanText( 'در صورت فعال بودن، دستیار شما توسط سایرین قابل مشاهده بوده و مورد استفاده قرار می‌گیرد.', fontSize: 14, color: Theme.of(context) .colorScheme .caption, ), ), ], ) ], ), ), SizedBox( width: 64, height: 48, child: DidvanSwitch( value: !state.isPrivate, title: '', onChanged: (value) { state.isPrivate = !value; }, ), ), ], ), const SizedBox( height: 24, ), widget.id != null ? Flex( direction: Axis.horizontal, children: [ Flexible( flex: 2, child: Stack( children: [ DidvanButton( title: state.loadingCreate ? ' ' : 'ذخیره تغییرات', onPressed: () async => onConfirm(state), ), if (state.loadingCreate) const Positioned.fill( child: Center( child: SpinKitThreeBounce( size: 32, color: Colors.white, ), ), ) ], ), ), const SizedBox( width: 20, ), Flexible( flex: 1, child: DidvanButton( title: 'حذف دستیار', style: ButtonStyleMode.flat, color: Theme.of(context).colorScheme.error, onPressed: () { ActionSheetUtils(context).openDialog( data: ActionSheetData( title: 'حذف دستیار', titleIcon: DidvanIcons.trash_solid, titleColor: Theme.of(context) .colorScheme .error, content: const Column( children: [ DidvanText( 'با حذف این دستیار، استفاده از آن برای شما و سایر کاربران، امکان‌پذیر نیست.\nآیا مطمئن هستید؟!', fontSize: 14, ) ], ), onConfirmed: () async { final success = await state.deleteAssistants( id: widget.id!); if (success) { context .read() .getMyAssissmant(); context .read< CreateBotAssistantsState>() .assistant = null; Navigator.pop(context); } }, )); }, ), ), ], ) : Stack( children: [ DidvanButton( title: state.loadingCreate ? ' ' : 'ذخیره', onPressed: () async => onConfirm(state)), if (state.loadingCreate) const Positioned.fill( child: Center( child: SpinKitThreeBounce( size: 32, color: Colors.white, ), ), ) ], ), const SizedBox( height: 24, ), ], ), ), ); }), ), ); } Container botRow(BotsModel bot, BuildContext context) { return Container( alignment: Alignment.center, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: DesignConfig.highBorderRadius), child: Row( children: [ SkeletonImage( imageUrl: bot.image.toString(), width: 42, height: 42, borderRadius: BorderRadius.circular(360), ), const SizedBox(width: 12), Expanded( child: DidvanText( bot.name.toString(), maxLines: 1, overflow: TextOverflow.ellipsis, )) ], ), ); } Widget title({required final String text, final bool isRequired = true}) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ Text.rich( TextSpan( children: [ TextSpan( text: text, style: Theme.of(context).textTheme.bodyMedium, ), if (isRequired) TextSpan( text: '*', style: Theme.of(context).textTheme.bodyMedium!.copyWith( color: Theme.of(context).colorScheme.error)), ], ), ), ], ), ); } }