From e2cbe435ba6db1c4aff32bdc04c6bd838a7132f3 Mon Sep 17 00:00:00 2001 From: 20120454 Date: Thu, 14 Dec 2023 21:11:56 +0700 Subject: [PATCH] message api and db --- lib/core/widgets/chat_thread.dart | 10 +- lib/core/widgets/main_drawer.dart | 36 +++--- lib/data/local/constants/db_constants.dart | 4 +- .../message/message_datasource.dart | 114 ++++++++++++++++++ .../network/apis/message/message_api.dart | 40 ++++++ lib/data/network/constants/endpoints.dart | 8 +- .../message/message_repository_impl.dart | 40 ++++++ lib/domain/entity/message/chat_thread.dart | 23 ++++ lib/domain/entity/message/message.dart | 24 ++++ .../entity/message/message_with_time.dart | 18 +++ .../message/message_repository.dart | 12 ++ 11 files changed, 300 insertions(+), 29 deletions(-) create mode 100644 lib/data/local/datasources/message/message_datasource.dart create mode 100644 lib/data/network/apis/message/message_api.dart create mode 100644 lib/data/repository/message/message_repository_impl.dart create mode 100644 lib/domain/entity/message/chat_thread.dart create mode 100644 lib/domain/entity/message/message.dart create mode 100644 lib/domain/entity/message/message_with_time.dart create mode 100644 lib/domain/repository/message/message_repository.dart diff --git a/lib/core/widgets/chat_thread.dart b/lib/core/widgets/chat_thread.dart index 47da53f..251b03f 100644 --- a/lib/core/widgets/chat_thread.dart +++ b/lib/core/widgets/chat_thread.dart @@ -1,18 +1,16 @@ -import 'dart:ffi'; - import 'package:flutter/material.dart'; -class ChatThread extends StatefulWidget { +class ChatThreadWidget extends StatefulWidget { final String name; final VoidCallback onTap; - const ChatThread({Key? key, required this.name, required this.onTap}) + const ChatThreadWidget({Key? key, required this.name, required this.onTap}) : super(key: key); @override - State createState() => _ChatThreadState(); + State createState() => _ChatThreadWidgetState(); } -class _ChatThreadState extends State { +class _ChatThreadWidgetState extends State { @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/core/widgets/main_drawer.dart b/lib/core/widgets/main_drawer.dart index f55cba4..6959e4e 100644 --- a/lib/core/widgets/main_drawer.dart +++ b/lib/core/widgets/main_drawer.dart @@ -31,109 +31,109 @@ class MainDrawer extends StatelessWidget { child: ListView( padding: EdgeInsets.zero, children: [ - ChatThread( + ChatThreadWidget( name: 'GraphQL in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'REST API Explanation', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Jitsi Meet in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Websockets in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'ChatGPT Flutter App', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'GraphQL in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'REST API Explanation', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Jitsi Meet in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Websockets in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'GraphQL in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'REST API Explanation', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Jitsi Meet in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Websockets in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'ChatGPT Flutter App', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'GraphQL in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'REST API Explanation', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Jitsi Meet in Flutter', onTap: () { setChatThreadId('1'); }, ), - ChatThread( + ChatThreadWidget( name: 'Websockets in Flutter', onTap: () { setChatThreadId('1'); diff --git a/lib/data/local/constants/db_constants.dart b/lib/data/local/constants/db_constants.dart index 25ba1b0..1d2e3ce 100644 --- a/lib/data/local/constants/db_constants.dart +++ b/lib/data/local/constants/db_constants.dart @@ -2,10 +2,10 @@ class DBConstants { DBConstants._(); // Store Name - static const String STORE_NAME = 'demo'; + static const String STORE_NAME = 'chatGPT'; // DB Name - static const DB_NAME = 'demo.db'; + static const DB_NAME = 'chatGPT.db'; // Fields static const FIELD_ID = 'id'; diff --git a/lib/data/local/datasources/message/message_datasource.dart b/lib/data/local/datasources/message/message_datasource.dart new file mode 100644 index 0000000..8c12ec3 --- /dev/null +++ b/lib/data/local/datasources/message/message_datasource.dart @@ -0,0 +1,114 @@ +import 'package:boilerplate/core/data/local/sembast/sembast_client.dart'; +import 'package:boilerplate/data/local/constants/db_constants.dart'; +import 'package:boilerplate/domain/entity/message/chat_thread.dart'; +import 'package:boilerplate/domain/entity/post/post.dart'; +import 'package:boilerplate/domain/entity/post/post_list.dart'; +import 'package:sembast/sembast.dart'; + +class MessageDataSource { + // A Store with int keys and Map values. + // This Store acts like a persistent map, values of which are Flogs objects converted to Map + final _messageStore = intMapStoreFactory.store(DBConstants.STORE_NAME); + + // Private getter to shorten the amount of code needed to get the + // singleton instance of an opened database. +// Future get _db async => await AppDatabase.instance.database; + + // database instance + final SembastClient _sembastClient; + + // Constructor + MessageDataSource(this._sembastClient); + + // DB functions:-------------------------------------------------------------- + Future insertChatThread(ChatThread chatThread) async { + int id = await _messageStore + .record(chatThread.id) + .add(_sembastClient.database, chatThread.toMap()) ?? + -1; + return id; + } + + Future count() async { + return await _messageStore.count(_sembastClient.database); + } + + Future> getChatThreads({List? filters}) async { + //creating finder + final finder = Finder( + filter: filters != null ? Filter.and(filters) : null, + sortOrders: [SortOrder(DBConstants.FIELD_ID)]); + + final recordSnapshots = await _messageStore.find( + _sembastClient.database, + finder: finder, + ); + + // Making a List out of List + return recordSnapshots.map((snapshot) { + final chatThread = ChatThread.fromMap(snapshot.value); + // An ID is a key of a record from the database. + chatThread.id = snapshot.key; + return chatThread; + }).toList(); + } + + Future getAllChatThreads() async { + print('Loading from database'); + + // post list + var chatThreadList; + + // fetching data + final recordSnapshots = await _messageStore.find( + _sembastClient.database, + ); + + // Making a List out of List + if (recordSnapshots.length > 0) { + chatThreadList = recordSnapshots.map((snapshot) { + final chatThread = ChatThread.fromMap(snapshot.value); + // An ID is a key of a record from the database. + chatThread.id = snapshot.key; + return chatThread; + }).toList(); + } + + return chatThreadList; + } + + Future getChatThreadById(int id) async { + final finder = Finder(filter: Filter.byKey(id)); + final recordSnapshots = await _messageStore.findFirst( + _sembastClient.database, + finder: finder, + ); + final chatThread = ChatThread.fromMap(recordSnapshots!.value); + return chatThread; + } + + Future update(Post post) async { + // For filtering by key (ID), RegEx, greater than, and many other criteria, + // we use a Finder. + final finder = Finder(filter: Filter.byKey(post.id)); + return await _messageStore.update( + _sembastClient.database, + post.toMap(), + finder: finder, + ); + } + + Future delete(Post post) async { + final finder = Finder(filter: Filter.byKey(post.id)); + return await _messageStore.delete( + _sembastClient.database, + finder: finder, + ); + } + + Future deleteAll() async { + await _messageStore.drop( + _sembastClient.database, + ); + } +} diff --git a/lib/data/network/apis/message/message_api.dart b/lib/data/network/apis/message/message_api.dart new file mode 100644 index 0000000..23f8073 --- /dev/null +++ b/lib/data/network/apis/message/message_api.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:boilerplate/core/data/network/dio/dio_client.dart'; +import 'package:boilerplate/data/network/constants/endpoints.dart'; +import 'package:boilerplate/data/network/rest_client.dart'; +import 'package:boilerplate/domain/entity/message/message.dart'; + +class MessageApi { + // dio instance + final DioClient _dioClient; + + // rest-client instance + final RestClient _restClient; + + // injecting dio instance + MessageApi(this._dioClient, this._restClient); + + Future sendMessage(List messages) async { + try { + final res = await _dioClient.dio.post(Endpoints.chat, data: { + "messages": messages.map((e) => e.toMap()).toList(), + "model": "gpt-3.5-turbo", + "temperature": "0.5" + }); + return Message.fromMap(res.data["choices"][0]["message"]); + } catch (e) { + print(e.toString()); + throw e; + } + } + + /// sample api call with default rest client +// Future getPosts() { +// +// return _restClient +// .get(Endpoints.getPosts) +// .then((dynamic res) => PostsList.fromJson(res)) +// .catchError((error) => throw NetworkException(message: error)); +// } +} diff --git a/lib/data/network/constants/endpoints.dart b/lib/data/network/constants/endpoints.dart index 160227a..33d6410 100644 --- a/lib/data/network/constants/endpoints.dart +++ b/lib/data/network/constants/endpoints.dart @@ -2,14 +2,16 @@ class Endpoints { Endpoints._(); // base url - static const String baseUrl = "http://jsonplaceholder.typicode.com"; + static const String baseUrl = "https://api.openai.com/v1"; // receiveTimeout - static const int receiveTimeout = 15000; + static const int receiveTimeout = 60000; // connectTimeout static const int connectionTimeout = 30000; // booking endpoints static const String getPosts = baseUrl + "/posts"; -} \ No newline at end of file + + static const String chat = baseUrl + '/chat/completions'; +} diff --git a/lib/data/repository/message/message_repository_impl.dart b/lib/data/repository/message/message_repository_impl.dart new file mode 100644 index 0000000..3808440 --- /dev/null +++ b/lib/data/repository/message/message_repository_impl.dart @@ -0,0 +1,40 @@ +import 'dart:async'; +import 'package:boilerplate/data/local/datasources/message/message_datasource.dart'; +import 'package:boilerplate/data/network/apis/message/message_api.dart'; +import 'package:boilerplate/domain/entity/message/chat_thread.dart'; +import 'package:boilerplate/domain/entity/message/message.dart'; +import 'package:boilerplate/domain/repository/message/message_repository.dart'; + +class MessageRepositoryImpl extends MessageRepository { + // data source object + final MessageDataSource _messageDataSource; + + // api objects + final MessageApi _messageApi; + + // constructor + MessageRepositoryImpl(this._messageApi, this._messageDataSource); + + // Post: --------------------------------------------------------------------- + @override + Future sendMessage(List messages) async { + // check to see if posts are present in database, then fetch from database + // else make a network call to get all posts, store them into database for + // later use + return await _messageApi.sendMessage(messages).then((value) { + return value; + }).catchError((error) => throw error); + } + + @override + Future getChatThread(int id) => _messageDataSource + .getChatThreadById(id) + .then((id) => id) + .catchError((error) => throw error); + + @override + Future saveChatThread(ChatThread chatThread) => _messageDataSource + .insertChatThread(chatThread) + .then((id) => id) + .catchError((error) => throw error); +} diff --git a/lib/domain/entity/message/chat_thread.dart b/lib/domain/entity/message/chat_thread.dart new file mode 100644 index 0000000..f0ed3b1 --- /dev/null +++ b/lib/domain/entity/message/chat_thread.dart @@ -0,0 +1,23 @@ +import 'package:boilerplate/domain/entity/message/message_with_time.dart'; + +class ChatThread { + int id; + String subject; + List messages; + ChatThread({ + required this.id, + required this.subject, + required this.messages, + }); + factory ChatThread.fromMap(Map json) => ChatThread( + id: json["id"], + subject: json["subject"], + messages: List.from( + json["messages"].map((x) => MessageWithTime.fromMap(x))), + ); + Map toMap() => { + "id": id, + "subject": subject, + "messages": List.from(messages.map((x) => x.toMap())), + }; +} diff --git a/lib/domain/entity/message/message.dart b/lib/domain/entity/message/message.dart new file mode 100644 index 0000000..74c3e69 --- /dev/null +++ b/lib/domain/entity/message/message.dart @@ -0,0 +1,24 @@ +enum Role { + user, + assistant, +} + +class Message { + Role role; + String content; + + Message({ + required this.role, + required this.content, + }); + + factory Message.fromMap(Map json) => Message( + role: json["role"], + content: json["content"], + ); + + Map toMap() => { + "role": role, + "content": content, + }; +} diff --git a/lib/domain/entity/message/message_with_time.dart b/lib/domain/entity/message/message_with_time.dart new file mode 100644 index 0000000..3888b93 --- /dev/null +++ b/lib/domain/entity/message/message_with_time.dart @@ -0,0 +1,18 @@ +import 'package:boilerplate/domain/entity/message/message.dart'; + +class MessageWithTime { + final Message message; + final DateTime time; + + MessageWithTime(this.message, this.time); + + factory MessageWithTime.fromMap(Map json) => MessageWithTime( + Message.fromMap(json["message"]), + DateTime.parse(json["time"]), + ); + + Map toMap() => { + "message": message.toMap(), + "time": time.toIso8601String(), + }; +} diff --git a/lib/domain/repository/message/message_repository.dart b/lib/domain/repository/message/message_repository.dart new file mode 100644 index 0000000..487d9ca --- /dev/null +++ b/lib/domain/repository/message/message_repository.dart @@ -0,0 +1,12 @@ +import 'dart:async'; + +import 'package:boilerplate/domain/entity/message/chat_thread.dart'; +import 'package:boilerplate/domain/entity/message/message.dart'; + +abstract class MessageRepository { + Future getChatThread(int id); + + Future sendMessage(List messages); + + Future saveChatThread(ChatThread chatThread); +}