349 lines
11 KiB
Dart
349 lines
11 KiB
Dart
// lib/views/ai/ai_chat_state.dart
|
||
|
||
// ignore_for_file: body_might_complete_normally_catch_error, avoid_print
|
||
|
||
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();
|
||
|
||
// --- ADDED ---
|
||
List<String> suggestedQuestions = [];
|
||
bool isLoadingQuestions = true;
|
||
// --- END ADDED ---
|
||
|
||
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();
|
||
print("error is ${e.toString()}");
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// --- ADDED ---
|
||
Future<void> fetchSuggestedQuestions() async {
|
||
isLoadingQuestions = true;
|
||
suggestedQuestions.clear();
|
||
update();
|
||
|
||
// 1. RequestHelper فقط URL string را برمیگرداند
|
||
final service = RequestService(
|
||
RequestHelper.aiSuggestedQuestions(),
|
||
// 2. بدنه درخواست خالی است و هدر Authorization خودکار اضافه میشود
|
||
);
|
||
|
||
// 3. متد post() را (به جای httpPost) فراخوانی میکنیم
|
||
await service.post();
|
||
|
||
if (service.isSuccess) {
|
||
// 4. از متد data() برای خواندن نتیجه استفاده میکنیم
|
||
final List<dynamic> questionsList = service.data('Questions');
|
||
if (questionsList.isNotEmpty) {
|
||
suggestedQuestions = questionsList.map((q) => q.toString()).toList();
|
||
}
|
||
isLoadingQuestions = false;
|
||
update();
|
||
} else {
|
||
// 5. از errorMessage برای نمایش خطا استفاده میکنیم
|
||
print("Error fetching suggested questions: ${service.errorMessage}");
|
||
isLoadingQuestions = false;
|
||
update();
|
||
}
|
||
}
|
||
// --- END ADDED ---
|
||
|
||
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();
|
||
|
||
// تعیین URL بر اساس bot ID
|
||
String url;
|
||
if (bot.id == 100) {
|
||
// برای Aisummery از endpoint خاص استفاده میکنیم
|
||
url = '/100/aisummery';
|
||
} else {
|
||
// برای بقیه bot ها از endpoint عادی استفاده میکنیم
|
||
url = '${isAssistants ? '/user/${bot.responseType}' : ''}/${bot.id}/${bot.name}'
|
||
.toLowerCase();
|
||
}
|
||
|
||
final req = await AiApiService.initial(
|
||
url: url,
|
||
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('{{{');
|
||
|
||
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();
|
||
}
|
||
|
||
Future<void> clearChat() async {
|
||
messages.clear();
|
||
chatId = null;
|
||
chat = null;
|
||
file = null;
|
||
message.clear();
|
||
isEdite = false;
|
||
onResponsing = false;
|
||
loading = false;
|
||
notifyListeners();
|
||
await Future.delayed(const Duration(milliseconds: 10));
|
||
}
|
||
} |