friday coffegit config

This commit is contained in:
kiainsta23 2024-09-27 15:35:01 +03:30
parent 9531a112d4
commit 6dad9ad68b
11 changed files with 234 additions and 204 deletions

View File

@ -1,5 +1,5 @@
import 'package:didvan/models/ai/bots_model.dart';
import 'package:didvan/models/ai/files_model.dart';
import 'package:image_picker/image_picker.dart';
class ChatsModel {
int? id;
@ -98,7 +98,6 @@ class Prompts {
bool? finished;
bool? error;
bool? audio;
FilesModel? fileLocal;
int? duration;
Prompts(
@ -111,7 +110,6 @@ class Prompts {
this.createdAt,
this.finished,
this.error,
this.fileLocal,
this.audio,
this.duration});
@ -147,7 +145,7 @@ class Prompts {
String? text,
String? file,
String? fileName,
FilesModel? fileLocal,
XFile? fileLocal,
String? role,
String? createdAt,
bool? finished,
@ -161,7 +159,6 @@ class Prompts {
text: text ?? this.text,
file: file ?? this.file,
fileName: fileName ?? this.fileName,
fileLocal: fileLocal ?? this.fileLocal,
role: role ?? this.role,
createdAt: createdAt ?? this.createdAt,
finished: finished ?? this.finished,

View File

@ -0,0 +1,5 @@
enum MyFileType{
image,
audio,
file
}

View File

@ -1,45 +1,45 @@
import 'dart:io';
import 'dart:typed_data';
// import 'dart:io';
// import 'dart:typed_data';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p;
// import 'package:mime/mime.dart';
// import 'package:path/path.dart' as p;
class FilesModel {
final String path;
late String basename;
late String extname;
late File main;
final bool isRecorded;
final bool? audio;
final bool? image;
final bool? network;
final Uint8List? bytes;
final Duration? duration;
// class FilesModel {
// final String path;
// late String basename;
// late String extname;
// late File main;
// final bool isRecorded;
// final bool? audio;
// final bool? image;
// final bool? network;
// final Uint8List? bytes;
// final Duration? duration;
FilesModel(
this.path, {
final String? name,
this.isRecorded = false,
this.audio,
this.image,
this.network,
this.bytes,
this.duration,
}) {
basename = name ?? p.basename(path);
extname = p.extension(path);
main = File(path);
}
// FilesModel(
// this.path, {
// final String? name,
// this.isRecorded = false,
// this.audio,
// this.image,
// this.network,
// this.bytes,
// this.duration,
// }) {
// basename = name ?? p.basename(path);
// extname = path.isNotEmpty ? p.extension(path): name !=null ? p.extension(name): '';
// main = File(path);
// }
bool isAudio() {
return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false;
}
// bool isAudio() {
// return audio ?? lookupMimeType(path)?.startsWith('audio/') ?? false;
// }
bool isImage() {
return image ?? lookupMimeType(path)?.startsWith('image/') ?? false;
}
// bool isImage() {
// return image ?? lookupMimeType(path)?.startsWith('image/') ?? false;
// }
bool isNetwork() {
return network ?? path.startsWith('blob:') || path.startsWith('/uploads');
}
}
// bool isNetwork() {
// return network ?? path.startsWith('blob:') || path.startsWith('/uploads');
// }
// }

View File

@ -2,13 +2,12 @@
import 'dart:async';
import 'dart:convert';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
import 'package:mime/mime.dart';
class AiApiService {
static const String baseUrl = 'https://api.didvan.app/ai';
@ -17,7 +16,7 @@ class AiApiService {
{required final String url,
required final String message,
final int? chatId,
final FilesModel? file,
final XFile? file,
final bool? edite}) async {
final headers = {
"Authorization": "Bearer ${await StorageService.getValue(key: 'token')}",
@ -36,47 +35,61 @@ class AiApiService {
}
if (file != null) {
if (file.duration != null) {
request.fields['duration'] = file.duration!.inSeconds.toString();
}
// if (file.duration != null) {
// request.fields['duration'] = file.duration!.inSeconds.toString();
// }
Uint8List bytes;
String filename;
String mimeType;
if (kIsWeb) {
// For web platform
if (file.bytes != null) {
bytes = file.bytes!;
} else {
final Uri audioUri = Uri.parse(file.path.replaceAll('%3A', ':'));
final http.Response audioResponse = await http.get(audioUri);
bytes = audioResponse.bodyBytes;
// final f = File.fromUri(Uri.parse(file.path));
// bytes = await f.readAsBytes();
// Fetch the blob using JavaScript interop
// final blob = await html.window
// .fetch(file.path.replaceAll('%3A', ':'))
// .then((response) => response.blob());
bytes = await file.readAsBytes();
filename = file.name;
mimeType = lookupMimeType(filename) ?? 'application/octet-stream';
print("mimeType: $mimeType");
// // Read the blob as an array buffer
// final reader = html.FileReader();
// reader.readAsArrayBuffer(blob);
// await reader.onLoadEnd.first;
// bytes = reader.result as Uint8List;
}
} else {
// For other platforms
bytes = await file.main.readAsBytes();
}
filename = file.basename;
mimeType = file.isAudio()
? file.isRecorded
? 'audio/m4a}'
: 'audio/${file.extname.replaceAll('.', '')}'
: file.isImage()
? 'image/png'
: 'application/pdf';
// switch (file.extname) {
// case '.mp3':
// mimeType = 'audio/mpeg';
// break;
// case '.wav':
// mimeType = 'audio/wav';
// break;
// case '.aac':
// mimeType = 'audio/aac';
// break;
// case '.m4a':
// mimeType = 'audio/x-m4a'; // or 'audio/aac'
// break;
// case '.ogg':
// mimeType = 'audio/ogg';
// break;
// case '.flac':
// mimeType = 'audio/x-flac';
// break;
// case '.wma':
// mimeType = 'audio/x-ms-wma';
// break;
// case '.amr':
// mimeType = 'audio/amr';
// break;
// case '.midi':
// mimeType = 'audio/midi';
// break;
// case '.weba':
// mimeType = 'audio/webm';
// break;
// case '.png':
// mimeType = 'image/png';
// break;
// case '.jpg':
// mimeType = 'image/jpeg';
// break;
// case '.pdf':
// mimeType = 'application/pdf';
// break;
// default:
// mimeType = lookupMimeType(filename) ?? 'application/octet-stream';
// }
request.files.add(http.MultipartFile.fromBytes(
'file',
@ -86,7 +99,6 @@ class AiApiService {
));
}
// print("req: ${request.files}");
// print("req: ${request.fields}");
return request;

View File

@ -1,6 +1,7 @@
// ignore_for_file: avoid_web_libraries_in_flutter
import 'package:didvan/main.dart';
import 'package:didvan/models/ai/file_type.dart';
import 'package:didvan/models/notification_message.dart';
import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart';
@ -16,6 +17,7 @@ import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:path/path.dart' as p;
class AppInitializer {
static String? fcmToken;
@ -204,4 +206,34 @@ class AppInitializer {
Navigator.of(context).pushNamed(Routes.web, arguments: src);
}
}
static MyFileType getFileType(String extName) {
MyFileType result;
switch (p.extension(extName)) {
case '.mp3':
case '.wav':
case '.aac':
case '.m4a':
case '.ogg':
case '.flac':
case '.wma':
case '.amr':
case '.midi':
case '.weba':
result = MyFileType.audio;
break;
case '.png':
case '.jpg':
result = MyFileType.image;
break;
case '.pdf':
result = MyFileType.file;
break;
default:
result = MyFileType.file;
}
return result;
}
}

View File

@ -6,7 +6,6 @@ import 'package:didvan/services/notification/notification_service.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class FirebaseApi {

View File

@ -1,5 +1,7 @@
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
@ -7,10 +9,11 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/chats_model.dart';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/models/ai/file_type.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/app_initalizer.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/ai_chat_state.dart';
@ -26,6 +29,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:image_picker/image_picker.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
@ -268,10 +272,12 @@ class _AiChatPageState extends State<AiChatPage> {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: state.file != null &&
!state.file!.isRecorded
bottom:
state.file != null &&
!state.isRecorded
? 150
: 100),
:
100),
itemBuilder: (context, mIndex) {
final prompts = state.messages[mIndex].prompts;
final time = state.messages[mIndex].dateTime;
@ -329,13 +335,12 @@ class _AiChatPageState extends State<AiChatPage> {
Widget messageBubble(Prompts message, BuildContext context, AiChatState state,
int index, int mIndex) {
FilesModel? file = message.fileLocal ??
(message.file == null
? null
: FilesModel(message.file.toString(),
duration: message.duration != null
? Duration(seconds: message.duration!)
: null));
String? fileUrl = message.file;
String? fileName = message.fileName;
MyFileType? fileType ;
if(fileName != null){
fileType = AppInitializer.getFileType(fileName);
}
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
pPadding: const EdgeInsets.all(0.8),
@ -431,22 +436,22 @@ class _AiChatPageState extends State<AiChatPage> {
: Column(
children: [
if (message.role.toString().contains('user') &&
file != null)
(file.isAudio()
fileUrl != null && fileName != null)
(fileType == MyFileType.audio
// && (!kIsWeb && !Platform.isIOS)
)
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
child: AudioWave(
file: file.path,
totalDuration: file.duration,
file: fileUrl,
// totalDuration: file.duration,
),
)
: file.isImage()
: fileType == MyFileType.image
? Padding(
padding: const EdgeInsets.all(8.0),
child: messageImage(file),
child: messageImage(fileUrl),
)
: Padding(
padding: const EdgeInsets.all(
@ -509,6 +514,7 @@ class _AiChatPageState extends State<AiChatPage> {
.remove(message);
state.messages.last.prompts.add(
message.copyWith(error: false));
state.file = state.messages.last.prompts.last.file!= null? XFile(state.messages.last.prompts.last.file!): null;
state.update();
await state
.postMessage(widget.args.bot);
@ -626,7 +632,7 @@ class _AiChatPageState extends State<AiChatPage> {
),
if (state.file != null && !kIsWeb)
FutureBuilder(
future: state.file!.main.length(),
future: state.file!.length(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
@ -644,23 +650,23 @@ class _AiChatPageState extends State<AiChatPage> {
);
}
Widget messageImage(FilesModel file) {
Widget messageImage(String file) {
return GestureDetector(
onTap: () => ActionSheetUtils(context)
.openInteractiveViewer(context, file.path, !file.isNetwork()),
child: file.isNetwork()
? file.path.startsWith('blob:')
.openInteractiveViewer(context, file, !(file.startsWith('blob:') || file.startsWith('/uploads'))),
child: (file.startsWith('blob:') || file.startsWith('/uploads'))
? file.startsWith('blob:')
? ClipRRect(
borderRadius: DesignConfig.lowBorderRadius,
child: Image.network(file.path))
child: Image.network(file))
: SkeletonImage(
pWidth: MediaQuery.sizeOf(context).width / 1,
pHeight: MediaQuery.sizeOf(context).height / 6,
imageUrl: file.path,
imageUrl: file,
)
: ClipRRect(
borderRadius: DesignConfig.lowBorderRadius,
child: Image.file(file.main)),
child: Image.file(File(file))),
);
}
}

View File

@ -6,11 +6,11 @@ import 'package:didvan/main.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.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/files_model.dart';
import 'package:didvan/models/ai/messages_model.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/alert_data.dart';
@ -33,7 +33,8 @@ class AiChatState extends CoreProvier {
final ScrollController scrollController = ScrollController();
int? chatId;
ChatsModel? chat;
FilesModel? file;
XFile? file;
bool isRecorded = false;
TextEditingController message = TextEditingController();
Future<void> _scrolledEnd() async {
@ -159,6 +160,10 @@ class AiChatState extends CoreProvier {
chatId: chatId,
file: file,
edite: isEdite);
file = null;
isRecorded = false;
update();
final res = await AiApiService().getResponse(req).catchError((e) {
_onError(e);
// return e;
@ -166,7 +171,6 @@ class AiChatState extends CoreProvier {
String responseMessgae = '';
String dataMessgae = '';
file = null;
update();
final r = res.listen((value) async {

View File

@ -6,11 +6,10 @@ import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/ai/bots_model.dart';
import 'package:didvan/models/ai/chats_model.dart';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/models/ai/file_type.dart';
import 'package:didvan/models/ai/messages_model.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/media/voice.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/ai_chat_state.dart';
@ -33,6 +32,7 @@ import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:record/record.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
class AiMessageBar extends StatefulWidget {
@ -126,8 +126,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
ignoring: state.onResponsing,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 24).copyWith(
top: openAttach ||
(state.file != null && !state.file!.isRecorded)
top: openAttach
||(state.file != null && !state.isRecorded)
? 0
: 24),
decoration: BoxDecoration(
@ -162,25 +162,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
FilePickerResult? result =
await MediaService.pickPdfFile();
if (result != null) {
if (kIsWeb) {
Uint8List? bytes = result.files.first
.bytes; // Access the bytes property
String? name = result.files.first.name;
// Store bytes and file name directly in your state or model
state.file = FilesModel(
'', // No need for a file path on web
name: name,
bytes: bytes,
audio: false,
image: false,
);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: false,
image: false);
}
state.file = result.files.first.xFile;
openAttach = false;
}
@ -234,13 +216,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
return;
}
state.file = kIsWeb
? FilesModel(pickedFile.path,
name: pickedFile.name,
image: true,
audio: false)
: FilesModel(file!.path,
image: true, audio: false);
state.file =
kIsWeb ? pickedFile : XFile(file!.path);
openAttach = false;
await Future.delayed(
Duration.zero,
@ -261,24 +238,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
FilePickerResult? result =
await MediaService.pickAudioFile();
if (result != null) {
if (kIsWeb) {
Uint8List? bytes = result.files.first
.bytes; // Access the bytes property
String? name = result.files.first.name;
state.file = result.files.first.xFile;
state.file = FilesModel(
'', // No need for a file path on web
name: name,
bytes: bytes,
audio: true,
image: false,
);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: true,
image: false);
}
openAttach = false;
}
await Future.delayed(
@ -339,21 +300,29 @@ class _AiMessageBarState extends State<AiMessageBar> {
path = await record
.stop();
Duration? duration =
await VoiceService
.getDuration(
src: path ??
'');
// Duration? duration =
// await VoiceService
// .getDuration(
// src: path ??
// '');
state.file = FilesModel(
path.toString(),
name:
'${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a',
isRecorded: true,
audio: true,
image: false,
duration:
duration);
final Uri audioUri =
Uri.parse(path!
.replaceAll(
'%3A',
':'));
final http.Response
audioResponse =
await http.get(
audioUri);
final bytes =
audioResponse
.bodyBytes;
state.file = kIsWeb
? XFile.fromData(
bytes)
: XFile(path!);
state.isRecorded = true;
_timer.cancel();
_countTimer.value = 0;
state.update();
@ -409,9 +378,11 @@ class _AiMessageBarState extends State<AiMessageBar> {
)
: MessageBarBtn(
enable: (state.file !=
null &&
state.file!
.isRecorded) ||
null
&&
state
.isRecorded
) ||
(widget.bot
.attachment ==
1) ||
@ -421,10 +392,11 @@ class _AiMessageBarState extends State<AiMessageBar> {
.send_light,
click: () async {
if ((state.file ==
null ||
null
||
!state
.file!
.isRecorded) &&
.isRecorded
) &&
(widget.bot
.attachment !=
1) &&
@ -459,10 +431,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
?.path,
fileName: state
.file
?.basename,
fileLocal:
state
.file,
?.name,
finished:
true,
role: 'user',
@ -495,9 +464,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
?.path,
fileName: state
.file
?.basename,
fileLocal:
state.file,
?.name,
role:
'user',
createdAt: DateTime.now()
@ -585,9 +552,10 @@ class _AiMessageBarState extends State<AiMessageBar> {
],
),
)
: state.file != null &&
state.file!
.isRecorded
: state.file != null
&&
AppInitializer.getFileType(state.file!.name)
== MyFileType.audio && state.isRecorded
? audioContainer()
: Form(
child:
@ -803,9 +771,16 @@ class _AiMessageBarState extends State<AiMessageBar> {
}
AnimatedVisibility fileContainer() {
late MyFileType fileType;
final state = context.watch<AiChatState>();
if(state.file != null ){
fileType = AppInitializer.getFileType(state.file!.name);
}
return AnimatedVisibility(
isVisible: state.file != null && !state.file!.isRecorded,
isVisible: state.file != null
&& !state.isRecorded
,
duration: DesignConfig.lowAnimationDuration,
child: Container(
decoration: BoxDecoration(
@ -816,19 +791,19 @@ class _AiMessageBarState extends State<AiMessageBar> {
margin: const EdgeInsets.only(bottom: 8, left: 12, right: 12),
child: Row(
children: [
state.file != null && state.file!.isImage()
state.file != null && (fileType == MyFileType.image)
? SizedBox(
width: 32,
height: 42,
child: ClipRRect(
borderRadius: DesignConfig.lowBorderRadius,
child: state.file!.isNetwork()
child: state.file!.path.startsWith('blob:')
? Image.network(
state.file!.path,
fit: BoxFit.cover,
)
: Image.file(
state.file!.main,
File(state.file!.path),
fit: BoxFit.cover,
)))
: const Icon(Icons.file_copy),
@ -842,14 +817,14 @@ class _AiMessageBarState extends State<AiMessageBar> {
SizedBox(
height: 24,
child: MarqueeText(
text: state.file != null ? state.file!.basename : '',
text: state.file != null ? state.file!.name : '',
style: const TextStyle(fontSize: 14),
stop: const Duration(seconds: 3),
),
),
if (state.file != null && !kIsWeb)
FutureBuilder(
future: state.file!.main.length(),
future: state.file!.length(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();

View File

@ -348,7 +348,7 @@ class _ProfilePageState extends State<ProfilePage> {
),
const SizedBox(height: 16),
DidvanText(
'نسخه نرم‌افزار: 3.3.3',
'نسخه نرم‌افزار: 3.3.4',
style: Theme.of(context).textTheme.bodySmall,
),
],

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.3.3+3330
version: 3.3.4+3340
environment:
sdk: ">=2.19.0 <3.0.0"