didvan-app/lib/views/ai/ai_chat_state.dart

294 lines
9.1 KiB
Dart

// ignore_for_file: body_might_complete_normally_catch_error
import 'dart:async';
import 'dart:convert';
import 'package:didvan/main.dart';
import 'package:didvan/models/ai/files_model.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:didvan/models/ai/bots_model.dart';
import 'package:didvan/models/ai/chats_model.dart';
import 'package:didvan/models/ai/messages_model.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/providers/core.dart';
import 'package:didvan/services/ai/ai_api_service.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
class AiChatState extends CoreProvier {
ValueNotifier<Stream<String>> messageOnstream =
ValueNotifier(const Stream<String>.empty().asBroadcastStream());
List<MessageModel> messages = [];
bool onResponsing = false;
bool loading = false;
bool isEdite = false;
ValueNotifier<bool> changingPlaceHolder = ValueNotifier(false);
final ScrollController scrollController = ScrollController();
int? chatId;
ChatsModel? chat;
FilesModel? file;
bool isRecorded = false;
TextEditingController message = TextEditingController();
Future<void> _scrolledEnd() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await scrollController.animateTo(
scrollController.position.minScrollExtent,
duration: const Duration(milliseconds: 600),
curve: Curves.easeInOut,
);
});
}
Future<void> _onError(e) async {
onResponsing = false;
messages.last.prompts.removeLast();
messages.last.prompts.last =
messages.last.prompts.last.copyWith(error: true);
messageOnstream.value = const Stream.empty();
await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData(
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
update();
}
Future<RequestService> getChatId() async {
final service = RequestService(
RequestHelper.aiChatId(),
);
await service.httpGet();
if (service.isSuccess) {
final id = service.result['id'];
chatId = id;
chat ??= ChatsModel(id: chatId);
}
return service;
}
Future<void> getAllMessages(int chatId) async {
loading = true;
onResponsing = true;
update();
final service = RequestService(
RequestHelper.aiAChat(chatId),
);
await service.httpGet();
if (service.isSuccess) {
messages.clear();
final ChatsModel allMessages =
ChatsModel.fromJson(service.result['chat']);
for (var i = 0; i < allMessages.prompts!.length; i++) {
final prompt = allMessages.prompts![i];
if (messages.isNotEmpty &&
DateTime.parse(messages.last.prompts.last.createdAt.toString())
.toPersianDateStr()
.contains(DateTime.parse(prompt.createdAt.toString())
.toPersianDateStr())) {
messages.last.prompts.add(prompt);
} else {
messages.add(MessageModel(
dateTime: prompt.createdAt.toString(), prompts: [prompt]));
}
}
appState = AppState.idle;
loading = false;
onResponsing = false;
update();
return;
}
appState = AppState.failed;
loading = false;
onResponsing = false;
update();
}
Future<void> changePlaceHolder(String placeholder) async {
changingPlaceHolder.value = true;
update();
await Future.delayed(const Duration(seconds: 3));
final service = RequestService(RequestHelper.placeholder(chatId!),
body: {'placeholder': placeholder});
await service.put();
if (service.isSuccess) {
appState = AppState.idle;
if (chat == null) {
chat = ChatsModel(id: chatId, placeholder: placeholder);
} else {
chat = chat!.copyWith(placeholder: placeholder);
}
changingPlaceHolder.value = false;
update();
return;
}
appState = AppState.failed;
changingPlaceHolder.value = false;
update();
}
Future<void> postMessage(BotsModel bot, bool isAssistants) async {
onResponsing = true;
final uploadedFile = file;
file = null;
update();
String message = messages.last.prompts.last.text!;
messages.last.prompts.add(Prompts(
finished: false,
error: false,
text: '',
role: 'bot',
createdAt: DateTime.now()
.subtract(const Duration(minutes: 210))
.toIso8601String()));
update();
await _scrolledEnd();
// if (file != null) {
// html.AnchorElement anchorElement = html.AnchorElement(href: file!.path);
// anchorElement.download = '${file!.path}.m4a';
// anchorElement.click();
// }
final req = await AiApiService.initial(
url:
'${isAssistants ? '/user/${bot.responseType}' : ''}/${bot.id}/${bot.name}'
.toLowerCase(),
message: message,
chatId: chatId,
file: uploadedFile,
edite: isEdite);
file = null;
isRecorded = false;
update();
final res = await AiApiService().getResponse(req).catchError((e) {
_onError(e);
// return e;
});
String responseMessgae = '';
String dataMessgae = '';
update();
final r = res.listen((value) async {
var str = utf8.decode(value);
if (!kIsWeb) {
if (bot.responseType != 'text') {
responseMessgae += str.split('{{{').first;
}
if (str.contains('{{{')) {
dataMessgae += "{{{${str.split('{{{').last}";
update();
return;
}
}
responseMessgae += str;
if (kIsWeb) {
try {
int startIndex = responseMessgae.indexOf('{{{');
// + 3 to include the }}} characters
String slicedText =
responseMessgae.substring(startIndex, responseMessgae.length);
dataMessgae = slicedText;
responseMessgae = responseMessgae.replaceAll(dataMessgae, '');
} catch (e) {
e.printError();
}
if (bot.responseType != 'text') {
responseMessgae = "${responseMessgae.split('.png').first}.png";
return;
}
}
messageOnstream.value = Stream.value(responseMessgae);
update();
});
r.onDone(() async {
if (chatId == null) {
final service = await getChatId();
navigatorKey.currentContext!.read<HistoryAiChatState>().getChats();
if (!service.isSuccess) {
_onError(null);
return;
}
}
onResponsing = false;
if (responseMessgae.isEmpty) {
messages.last.prompts.removeLast();
messages.last.prompts.last =
messages.last.prompts.last.copyWith(error: true);
messageOnstream.value = const Stream.empty();
update();
_scrolledEnd();
return;
} else {
int? humanMessageId;
int? aiMessageId;
try {
final data = AppInitializer.messagesData(dataMessgae);
humanMessageId = data['HUMAN_MESSAGE_ID'];
aiMessageId = data['AI_MESSAGE_ID'];
} catch (e) {
e.printError();
return;
}
messages.last.prompts.last = messages.last.prompts.last.copyWith(
finished: true,
text: bot.responseType != 'text' ? null : responseMessgae,
file: bot.responseType != 'text' ? responseMessgae : null,
id: aiMessageId);
if (messages.last.prompts.length > 2) {
messages.last.prompts[messages.last.prompts.length - 2] = messages
.last.prompts[messages.last.prompts.length - 2]
.copyWith(id: humanMessageId);
} else {
messages.last.prompts.first =
messages.last.prompts.first.copyWith(id: humanMessageId);
}
messageOnstream.value = const Stream.empty();
}
if (isEdite) {
messages.last.prompts.removeAt((messages.last.prompts.length - 1) - 2);
messages.last.prompts.removeAt((messages.last.prompts.length - 1) - 2);
isEdite = false;
}
update();
_scrolledEnd();
});
r.onError((e) => _onError(e));
}
Future<void> deleteMessage(int id, int mIndex, int index) async {
final service = RequestService(RequestHelper.deleteMessage(chatId!, id));
await service.delete();
if (service.isSuccess) {
if (messages[mIndex].prompts.length <= 1) {
messages.removeAt(mIndex);
} else {
messages[mIndex].prompts.removeAt(index);
}
appState = AppState.idle;
update();
return;
}
appState = AppState.failed;
await ActionSheetUtils(navigatorKey.currentContext!).showAlert(AlertData(
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
update();
}
}