From dfd57937fb26968bb93e8e1f23045561bf44550b Mon Sep 17 00:00:00 2001 From: OkaykOrhmn Date: Sun, 17 Nov 2024 10:48:55 +0330 Subject: [PATCH] "Updated BotAssistantsReqModel to include description, modified file picker to allow any file type, added description field to CreateBotAssistantsPage, and made various other UI and logic changes." --- lib/models/ai/bot_assistants_req_model.dart | 3 + .../ai/file_create_assistants_model.dart | 10 + lib/services/media/media.dart | 32 +-- lib/services/network/request_helper.dart | 1 + lib/views/ai/bot_assistants_page.dart | 16 +- lib/views/ai/create_bot_assistants_page.dart | 255 ++++++++++++++---- lib/views/ai/create_bot_assistants_state.dart | 28 ++ lib/views/ai/info_page.dart | 8 +- 8 files changed, 277 insertions(+), 76 deletions(-) create mode 100644 lib/models/ai/file_create_assistants_model.dart diff --git a/lib/models/ai/bot_assistants_req_model.dart b/lib/models/ai/bot_assistants_req_model.dart index cc3bffa..f4b38ff 100644 --- a/lib/models/ai/bot_assistants_req_model.dart +++ b/lib/models/ai/bot_assistants_req_model.dart @@ -4,6 +4,7 @@ class BotAssistantsReqModel { final String type; final XFile? image; final String name; + final String description; final int botId; final String prompt; final List? files; @@ -16,6 +17,7 @@ class BotAssistantsReqModel { required this.name, required this.botId, required this.prompt, + required this.description, this.image, this.files, this.youtubeLink, @@ -28,6 +30,7 @@ class BotAssistantsReqModel { data['name'] = name; data['botId'] = botId; data['prompt'] = prompt; + data['description'] = description; data['isPrivate'] = isPrivate; if (youtubeLink != null) { data['youtubeLink'] = youtubeLink; diff --git a/lib/models/ai/file_create_assistants_model.dart b/lib/models/ai/file_create_assistants_model.dart new file mode 100644 index 0000000..96bcc56 --- /dev/null +++ b/lib/models/ai/file_create_assistants_model.dart @@ -0,0 +1,10 @@ +import 'package:image_picker/image_picker.dart'; + +class FileCreateAssistantsModel { + final bool fromNetwork; + final XFile? file; + final String? url; + + FileCreateAssistantsModel( + {required this.fromNetwork, required this.file, required this.url}); +} diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index 6607b87..3811264 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -141,22 +141,22 @@ class MediaService { try { return await FilePicker.platform .pickFiles( - type: FileType.custom, - allowedExtensions: [ - 'pdf', - 'doc', - 'docx', - 'xls', - 'xlsx', - 'ppt', - 'pptx', - 'txt', - 'mp3', - 'wav', - 'aac', - 'ogg', - 'flac' - ], // You can specify allowed extensions if needed + type: FileType.any, + // allowedExtensions: [ + // 'pdf', + // 'doc', + // 'docx', + // 'xls', + // 'xlsx', + // 'ppt', + // 'pptx', + // 'txt', + // 'mp3', + // 'wav', + // 'aac', + // 'ogg', + // 'flac' + // ], // You can specify allowed extensions if needed allowMultiple: true, // Note: The maxFiles parameter is not directly supported by FilePicker. // You will need to handle the limit after selection if necessary. diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 02544da..40e3dca 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -238,6 +238,7 @@ class RequestHelper { static String updateAssistants(int id) => '$baseUrl/ai/bot/$id'; static String getAssistant(int id) => '$baseUrl/ai/bot/user/$id'; static String deleteAssistant(int id) => '$baseUrl/ai/bot/user/$id'; + static String nameOfAssistant() => '$baseUrl/ai/bot/name'; static String _urlConcatGenerator(List> additions) { String result = ''; diff --git a/lib/views/ai/bot_assistants_page.dart b/lib/views/ai/bot_assistants_page.dart index a0efaa3..c899f74 100644 --- a/lib/views/ai/bot_assistants_page.dart +++ b/lib/views/ai/bot_assistants_page.dart @@ -227,14 +227,14 @@ class _BotAssistantsPageState extends State { const SizedBox( height: 8, ), - DidvanText( - assistants.description ?? - 'dsadsadsadadsadaddadadadsdadsad dsadsadsadadsadaddadadadsdadsad dsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsad vdsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsaddsadsadsadadsadaddadadadsdadsad', - fontSize: 12, - color: Theme.of(context).colorScheme.disabledText, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + if (assistants.description != null) + DidvanText( + assistants.description!, + fontSize: 12, + color: Theme.of(context).colorScheme.disabledText, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), const SizedBox( height: 18, ), diff --git a/lib/views/ai/create_bot_assistants_page.dart b/lib/views/ai/create_bot_assistants_page.dart index 98ae829..6d11a02 100644 --- a/lib/views/ai/create_bot_assistants_page.dart +++ b/lib/views/ai/create_bot_assistants_page.dart @@ -1,18 +1,22 @@ // 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_model.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/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/ai/history_ai_chat_state.dart'; @@ -22,6 +26,7 @@ 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'; @@ -59,12 +64,13 @@ class _CreateBotAssistantsPageState extends State { // String? youtubeLink; List countOfLink = ['']; - List files = []; + List files = []; ValueNotifier image = ValueNotifier(null); String selectedBotType = 'text'; bool isPrivate = true; BotAssistants? assistant; BotsModel? initialBot; + Timer? _timer; @override void initState() { @@ -92,20 +98,28 @@ class _CreateBotAssistantsPageState extends State { if (!isValid) { return; } + final List resultFiles = []; + for (var file in files) { + if (!file.fromNetwork) { + resultFiles.add(file.file!); + } + } await state.createAssistants( id: widget.id, data: BotAssistantsReqModel( type: selectedBotType, name: name, + description: desc, botId: selectedBotId, prompt: prompt, webLinks: countOfLink.first.isEmpty ? null : countOfLink, // youtubeLink: youtubeLink, isPrivate: isPrivate, - files: files, + files: resultFiles, image: image.value)); context.read().getMyAssissmant(); + context.read().assistant = null; Navigator.pop(context); } @@ -123,22 +137,28 @@ class _CreateBotAssistantsPageState extends State { }, child: Scaffold( appBar: HoshanAppBar( - onBack: () => Navigator.pop(context), + onBack: () { + context.read().assistant = null; + Navigator.pop(context); + }, withActions: false, ), body: Consumer(builder: (BuildContext context, CreateBotAssistantsState state, Widget? child) { - assistant = state.assistant; - if (assistant != null) { + if (assistant == null && state.assistant != null) { + assistant = state.assistant; + name = assistant!.name ?? ''; prompt = assistant!.prompt ?? ''; desc = assistant!.description ?? ''; // youtubeLink = assistant!.; isPrivate = assistant!.private ?? true; - if (assistant!.files != null && assistant!.files!.isNotEmpty) { + if (files.isEmpty && + assistant!.files != null && + assistant!.files!.isNotEmpty) { for (var file in assistant!.files!) { - final data = File.fromUri(Uri.parse(file)).readAsBytesSync(); - files.add(XFile.fromData(data)); + files.add(FileCreateAssistantsModel( + fromNetwork: true, file: null, url: file)); } } countOfLink = assistant!.websites ?? ['']; @@ -262,6 +282,16 @@ class _CreateBotAssistantsPageState extends State { initialValue: name, onChanged: (value) { name = value; + + if (value.isEmpty) { + return; + } + _timer?.cancel(); + _timer = + Timer(const Duration(seconds: 1), () async { + await state.getAssistantsName(name: value); + _formNameKey.currentState!.validate(); + }); }, validator: (value) { String? result; @@ -269,6 +299,9 @@ class _CreateBotAssistantsPageState extends State { result = 'نام نباید خالی باشد'; } else if (value.length < 4) { result = 'نام نباید کمتر از 4 حرف باشد'; + } else if (!state.successName) { + result = + 'اسم دیگری انتخاب کنید این اسم موجود است'; } return result; }, @@ -279,17 +312,26 @@ class _CreateBotAssistantsPageState extends State { const SizedBox( height: 8, ), + Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon( - DidvanIcons.info_circle_light, - color: Theme.of(context).colorScheme.caption, - ), + 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( - 'نام منحصر به فرد شامل 4 تا 20 کاراکتر (حروف، اعداد، خط تیره، نقطه و زیرخط) ', + state.loadingName + ? '...درحال بررسی اسم' + : 'نام منحصر به فرد شامل 4 تا 20 کاراکتر (حروف، اعداد، خط تیره، نقطه و زیرخط) ', textAlign: TextAlign.right, fontSize: 12, color: Theme.of(context).colorScheme.caption, @@ -416,45 +458,152 @@ class _CreateBotAssistantsPageState extends State { Column( children: [ title(text: 'پایگاه دانش', isRequired: false), - 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) { - files.addAll(picks.xFiles); - } + if (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) { + files.add(FileCreateAssistantsModel( + fromNetwork: false, + file: file, + url: null)); + } + } + 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( + files.length, + (index) { + return 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: files[index] + .fromNetwork + ? files[index] + .url! + .isImage() + ? CachedNetworkImage( + imageUrl: + files[index] + .url!) + : const Icon( + CupertinoIcons + .doc) + : files[index] + .file! + .path + .isImage() + ? Image.file(File( + files[index] + .file! + .path)) + : const Icon( + CupertinoIcons + .doc), + ), + MarqueeText( + text: files[index] + .fromNetwork + ? files[index] + .url! + .split('/') + .last + : files[index] + .file! + .name, + textDirection: + TextDirection.rtl, + style: Theme.of(context) + .textTheme + .labelSmall!) + ], + )), + Positioned( + top: 8, + left: 4, + child: InkWell( + onTap: () { + 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, + ), + ), + )) + ], + ); }, - 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, - ) - ], - )), + ) + ], ), const SizedBox( height: 24, @@ -648,6 +797,10 @@ class _CreateBotAssistantsPageState extends State { onConfirmed: () async { await state.deleteAssistants( id: widget.id!); + context + .read< + CreateBotAssistantsState>() + .assistant = null; Navigator.pop(context); }, )); diff --git a/lib/views/ai/create_bot_assistants_state.dart b/lib/views/ai/create_bot_assistants_state.dart index b512fae..a374fcb 100644 --- a/lib/views/ai/create_bot_assistants_state.dart +++ b/lib/views/ai/create_bot_assistants_state.dart @@ -11,6 +11,8 @@ class CreateBotAssistantsState extends CoreProvier { List imageBots = []; bool loadingImageBots = false; bool loadingCreate = false; + bool loadingName = false; + bool successName = false; bool loading = false; BotAssistants? assistant; @@ -100,4 +102,30 @@ class CreateBotAssistantsState extends CoreProvier { loadingCreate = false; update(); } + + Future getAssistantsName({required final String name}) async { + loadingName = true; + update(); + + final service = + RequestService(RequestHelper.nameOfAssistant(), body: {'name': name}); + await service.post(); + if (service.isSuccess) { + appState = AppState.idle; + loadingName = false; + if (service.result['available'] as bool) { + successName = true; + } else { + successName = false; + } + + update(); + return; + } + appState = AppState.failed; + loadingName = false; + successName = false; + + update(); + } } diff --git a/lib/views/ai/info_page.dart b/lib/views/ai/info_page.dart index ba30eb2..078ba3d 100644 --- a/lib/views/ai/info_page.dart +++ b/lib/views/ai/info_page.dart @@ -1,5 +1,6 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/hoshan_app_bar.dart'; @@ -205,7 +206,12 @@ class _InfoPageState extends State { ), const DidvanText('هنوز سوالی دارید؟'), TextButton( - onPressed: () {}, + onPressed: () { + Navigator.of(context).pushNamed( + Routes.direct, + arguments: {'type': 'پشتیبانی اپلیکیشن'}, + ); + }, child: const DidvanText( ' پیام به پشتیبانی', color: Color(0xff007EA7),