intermediate
This commit is contained in:
+18
@@ -0,0 +1,18 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
class ChatMetadataConverter extends TypeConverter<ChatMetadata, String> {
|
||||
const ChatMetadataConverter();
|
||||
|
||||
@override
|
||||
ChatMetadata fromSql(String fromDb) {
|
||||
return ChatMetadata.fromJson(jsonDecode(fromDb) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
@override
|
||||
String toSql(ChatMetadata value) {
|
||||
return jsonEncode(value.toJson());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/database.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:lexo_rank/lexo_rank.dart';
|
||||
|
||||
part 'messages.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class MessagesDao extends DatabaseAccessor<ChatDatabase>
|
||||
with _$MessagesDaoMixin {
|
||||
MessagesDao(super.db);
|
||||
|
||||
Selectable<int> messageCount({required String chatId}) {
|
||||
return db.chatMessage.count(
|
||||
where: (row) => row.chatId.equals(chatId),
|
||||
);
|
||||
}
|
||||
|
||||
Selectable<ChatMessageData> chatMessages({required String chatId}) {
|
||||
return db.chatMessage.select()
|
||||
..where((t) => t.chatId.equals(chatId))
|
||||
..orderBy([(row) => OrderingTerm(expression: row.orderKey)]);
|
||||
}
|
||||
|
||||
Selectable<bool> messageExists(String messageId) {
|
||||
final existsExpression = existsQuery(
|
||||
db.chatMessage.select()..where((row) => row.messageId.equals(messageId)),
|
||||
);
|
||||
|
||||
return db.selectExpressions([existsExpression]).map(
|
||||
(row) => row.read(existsExpression)!,
|
||||
);
|
||||
}
|
||||
|
||||
SingleOrNullSelectable<int> messageIndex({
|
||||
required String chatId,
|
||||
required String messageId,
|
||||
}) {
|
||||
return db
|
||||
.messageIndex(chatId: chatId, messageId: messageId)
|
||||
.map((rowId) => rowId - 1);
|
||||
}
|
||||
|
||||
Future<int?> insertMessage({
|
||||
required String chatId,
|
||||
required Message message,
|
||||
int? index,
|
||||
}) async {
|
||||
if (await messageExists(message.id).getSingle()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return db.transaction(
|
||||
() async {
|
||||
final String orderKey;
|
||||
if (index != null) {
|
||||
if (index <= 0) {
|
||||
orderKey =
|
||||
await db.leadingOrderKey(bucket: 0, chatId: chatId).getSingle();
|
||||
} else {
|
||||
orderKey = await db
|
||||
.orderKeyBeforeIndex(chatId: chatId, rowIdx: index + 1)
|
||||
.getSingle();
|
||||
}
|
||||
} else {
|
||||
orderKey =
|
||||
await db.trailingOrderKey(bucket: 0, chatId: chatId).getSingle();
|
||||
}
|
||||
|
||||
await db.chatMessage.insertOne(
|
||||
ChatMessageCompanion.insert(
|
||||
messageId: message.id,
|
||||
chatId: chatId,
|
||||
orderKey: orderKey,
|
||||
messageJson: jsonEncode(message.toJson()),
|
||||
),
|
||||
);
|
||||
|
||||
final rowIdx = await db
|
||||
.messageIndex(chatId: chatId, messageId: message.id)
|
||||
.getSingle();
|
||||
|
||||
return rowIdx - 1;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> removeMessage({required String messageId}) {
|
||||
return db.chatMessage.deleteWhere((row) => row.messageId.equals(messageId));
|
||||
}
|
||||
|
||||
Future<int> updateMessage({
|
||||
required String messageId,
|
||||
required Message message,
|
||||
}) {
|
||||
return (db.chatMessage.update()
|
||||
..where((row) => row.messageId.equals(messageId)))
|
||||
.write(
|
||||
ChatMessageCompanion(
|
||||
messageJson: Value(jsonEncode(message.toJson())),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> deleteChatMessages({required String chatId}) {
|
||||
return (db.chatMessage.delete()..where((t) => t.chatId.equals(chatId)))
|
||||
.go();
|
||||
}
|
||||
|
||||
Future<void> replaceChatMessages({
|
||||
required String chatId,
|
||||
required List<Message> messages,
|
||||
}) async {
|
||||
return db.transaction(() async {
|
||||
await deleteChatMessages(chatId: chatId);
|
||||
|
||||
var currentOrderKey =
|
||||
await db.leadingOrderKey(bucket: 0, chatId: chatId).getSingle();
|
||||
|
||||
await db.chatMessage.insertAll(
|
||||
messages.map(
|
||||
(message) {
|
||||
final insertable = ChatMessageCompanion.insert(
|
||||
messageId: message.id,
|
||||
chatId: chatId,
|
||||
messageJson: jsonEncode(message.toJson()),
|
||||
orderKey: currentOrderKey,
|
||||
// timestamp: DateTime.now(),
|
||||
);
|
||||
|
||||
currentOrderKey = LexoRank.parse(currentOrderKey).genPrev().value;
|
||||
|
||||
return insertable;
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'messages.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$MessagesDaoMixin on DatabaseAccessor<ChatDatabase> {}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/database.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
part 'metadata.g.dart';
|
||||
|
||||
@DriftAccessor()
|
||||
class MetadataDao extends DatabaseAccessor<ChatDatabase>
|
||||
with _$MetadataDaoMixin {
|
||||
MetadataDao(super.db);
|
||||
|
||||
SingleOrNullSelectable<ChatMetadata> chatMetadata(String chatId) {
|
||||
final statement = db.chatData.selectOnly()
|
||||
..addColumns([db.chatData.metadata])
|
||||
..where(db.chatData.chatId.equals(chatId));
|
||||
|
||||
return statement.map((row) => row.readWithConverter(db.chatData.metadata)!);
|
||||
}
|
||||
|
||||
Future<void> updateChatMetadata(String chatId, ChatMetadata metadata) {
|
||||
return db.chatData.insertOne(
|
||||
ChatDataCompanion.insert(chatId: chatId, metadata: metadata),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'metadata.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$MetadataDaoMixin on DatabaseAccessor<ChatDatabase> {}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/converters/chat_metadata_converter.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/daos/messages.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/daos/metadata.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
|
||||
import 'package:drift/drift.dart';
|
||||
|
||||
part 'database.g.dart';
|
||||
|
||||
@DriftDatabase(include: {'database.drift'}, daos: [MessagesDao, MetadataDao])
|
||||
class ChatDatabase extends _$ChatDatabase {
|
||||
ChatDatabase(super.e);
|
||||
|
||||
@override
|
||||
int get schemaVersion => 1;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/converters/chat_metadata_converter.dart';
|
||||
|
||||
CREATE TABLE chat_data (
|
||||
chat_id TEXT PRIMARY KEY NOT NULL,
|
||||
metadata TEXT NOT NULL MAPPED BY `const ChatMetadataConverter()`
|
||||
);
|
||||
|
||||
CREATE TABLE chat_message (
|
||||
message_id TEXT PRIMARY KEY NOT NULL,
|
||||
chat_id TEXT NOT NULL,
|
||||
order_key TEXT NOT NULL,
|
||||
message_json TEXT NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX group_order_idx on chat_message (chat_id, order_key);
|
||||
|
||||
leadingOrderKey:
|
||||
SELECT lexo_rank_previous(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM chat_message
|
||||
WHERE chat_id = :chat_id
|
||||
ORDER BY order_key
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
trailingOrderKey:
|
||||
SELECT lexo_rank_next(
|
||||
:bucket,
|
||||
(
|
||||
SELECT order_key
|
||||
FROM chat_message
|
||||
WHERE chat_id = :chat_id
|
||||
ORDER BY order_key DESC
|
||||
LIMIT 1
|
||||
)
|
||||
);
|
||||
|
||||
orderKeyBeforeIndex:
|
||||
WITH ordered_table AS
|
||||
(
|
||||
SELECT message_id,
|
||||
order_key,
|
||||
LEAD(order_key) OVER (ORDER BY order_key) AS next_order_key,
|
||||
ROW_NUMBER() OVER (ORDER BY order_key) AS row_idx,
|
||||
COUNT(*) OVER () as total_rows
|
||||
FROM chat_message
|
||||
WHERE chat_id = :chat_id
|
||||
)
|
||||
SELECT lexo_rank_reorder_after(order_key, next_order_key)
|
||||
FROM ordered_table
|
||||
WHERE row_idx = MIN(:row_idx, total_rows);
|
||||
|
||||
messageIndex:
|
||||
WITH ordered_table AS
|
||||
(
|
||||
SELECT message_id,
|
||||
ROW_NUMBER() OVER (ORDER BY order_key) AS row_idx
|
||||
FROM chat_message
|
||||
WHERE chat_id = :chat_id
|
||||
)
|
||||
SELECT row_idx
|
||||
FROM ordered_table
|
||||
WHERE message_id = :message_id;
|
||||
@@ -0,0 +1,764 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'database.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
class ChatData extends Table with TableInfo<ChatData, ChatDataData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
ChatData(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> chatId = GeneratedColumn<String>(
|
||||
'chat_id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumnWithTypeConverter<ChatMetadata, String> metadata =
|
||||
GeneratedColumn<String>('metadata', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL')
|
||||
.withConverter<ChatMetadata>(ChatData.$convertermetadata);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns => [chatId, metadata];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'chat_data';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {chatId};
|
||||
@override
|
||||
ChatDataData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return ChatDataData(
|
||||
chatId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}chat_id'])!,
|
||||
metadata: ChatData.$convertermetadata.fromSql(attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}metadata'])!),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatData createAlias(String alias) {
|
||||
return ChatData(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
static TypeConverter<ChatMetadata, String> $convertermetadata =
|
||||
const ChatMetadataConverter();
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class ChatDataData extends DataClass implements Insertable<ChatDataData> {
|
||||
final String chatId;
|
||||
final ChatMetadata metadata;
|
||||
const ChatDataData({required this.chatId, required this.metadata});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['chat_id'] = Variable<String>(chatId);
|
||||
{
|
||||
map['metadata'] =
|
||||
Variable<String>(ChatData.$convertermetadata.toSql(metadata));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
factory ChatDataData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return ChatDataData(
|
||||
chatId: serializer.fromJson<String>(json['chat_id']),
|
||||
metadata: serializer.fromJson<ChatMetadata>(json['metadata']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'chat_id': serializer.toJson<String>(chatId),
|
||||
'metadata': serializer.toJson<ChatMetadata>(metadata),
|
||||
};
|
||||
}
|
||||
|
||||
ChatDataData copyWith({String? chatId, ChatMetadata? metadata}) =>
|
||||
ChatDataData(
|
||||
chatId: chatId ?? this.chatId,
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
ChatDataData copyWithCompanion(ChatDataCompanion data) {
|
||||
return ChatDataData(
|
||||
chatId: data.chatId.present ? data.chatId.value : this.chatId,
|
||||
metadata: data.metadata.present ? data.metadata.value : this.metadata,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ChatDataData(')
|
||||
..write('chatId: $chatId, ')
|
||||
..write('metadata: $metadata')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(chatId, metadata);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ChatDataData &&
|
||||
other.chatId == this.chatId &&
|
||||
other.metadata == this.metadata);
|
||||
}
|
||||
|
||||
class ChatDataCompanion extends UpdateCompanion<ChatDataData> {
|
||||
final Value<String> chatId;
|
||||
final Value<ChatMetadata> metadata;
|
||||
final Value<int> rowid;
|
||||
const ChatDataCompanion({
|
||||
this.chatId = const Value.absent(),
|
||||
this.metadata = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
ChatDataCompanion.insert({
|
||||
required String chatId,
|
||||
required ChatMetadata metadata,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : chatId = Value(chatId),
|
||||
metadata = Value(metadata);
|
||||
static Insertable<ChatDataData> custom({
|
||||
Expression<String>? chatId,
|
||||
Expression<String>? metadata,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (chatId != null) 'chat_id': chatId,
|
||||
if (metadata != null) 'metadata': metadata,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
ChatDataCompanion copyWith(
|
||||
{Value<String>? chatId,
|
||||
Value<ChatMetadata>? metadata,
|
||||
Value<int>? rowid}) {
|
||||
return ChatDataCompanion(
|
||||
chatId: chatId ?? this.chatId,
|
||||
metadata: metadata ?? this.metadata,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (chatId.present) {
|
||||
map['chat_id'] = Variable<String>(chatId.value);
|
||||
}
|
||||
if (metadata.present) {
|
||||
map['metadata'] =
|
||||
Variable<String>(ChatData.$convertermetadata.toSql(metadata.value));
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ChatDataCompanion(')
|
||||
..write('chatId: $chatId, ')
|
||||
..write('metadata: $metadata, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class ChatMessage extends Table with TableInfo<ChatMessage, ChatMessageData> {
|
||||
@override
|
||||
final GeneratedDatabase attachedDatabase;
|
||||
final String? _alias;
|
||||
ChatMessage(this.attachedDatabase, [this._alias]);
|
||||
late final GeneratedColumn<String> messageId = GeneratedColumn<String>(
|
||||
'message_id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'PRIMARY KEY NOT NULL');
|
||||
late final GeneratedColumn<String> chatId = GeneratedColumn<String>(
|
||||
'chat_id', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
late final GeneratedColumn<String> orderKey = GeneratedColumn<String>(
|
||||
'order_key', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
late final GeneratedColumn<String> messageJson = GeneratedColumn<String>(
|
||||
'message_json', aliasedName, false,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
$customConstraints: 'NOT NULL');
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[messageId, chatId, orderKey, messageJson];
|
||||
@override
|
||||
String get aliasedName => _alias ?? actualTableName;
|
||||
@override
|
||||
String get actualTableName => $name;
|
||||
static const String $name = 'chat_message';
|
||||
@override
|
||||
Set<GeneratedColumn> get $primaryKey => {messageId};
|
||||
@override
|
||||
ChatMessageData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||
return ChatMessageData(
|
||||
messageId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}message_id'])!,
|
||||
chatId: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}chat_id'])!,
|
||||
orderKey: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}order_key'])!,
|
||||
messageJson: attachedDatabase.typeMapping
|
||||
.read(DriftSqlType.string, data['${effectivePrefix}message_json'])!,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatMessage createAlias(String alias) {
|
||||
return ChatMessage(attachedDatabase, alias);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get dontWriteConstraints => true;
|
||||
}
|
||||
|
||||
class ChatMessageData extends DataClass implements Insertable<ChatMessageData> {
|
||||
final String messageId;
|
||||
final String chatId;
|
||||
final String orderKey;
|
||||
final String messageJson;
|
||||
const ChatMessageData(
|
||||
{required this.messageId,
|
||||
required this.chatId,
|
||||
required this.orderKey,
|
||||
required this.messageJson});
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
map['message_id'] = Variable<String>(messageId);
|
||||
map['chat_id'] = Variable<String>(chatId);
|
||||
map['order_key'] = Variable<String>(orderKey);
|
||||
map['message_json'] = Variable<String>(messageJson);
|
||||
return map;
|
||||
}
|
||||
|
||||
factory ChatMessageData.fromJson(Map<String, dynamic> json,
|
||||
{ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return ChatMessageData(
|
||||
messageId: serializer.fromJson<String>(json['message_id']),
|
||||
chatId: serializer.fromJson<String>(json['chat_id']),
|
||||
orderKey: serializer.fromJson<String>(json['order_key']),
|
||||
messageJson: serializer.fromJson<String>(json['message_json']),
|
||||
);
|
||||
}
|
||||
@override
|
||||
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
|
||||
serializer ??= driftRuntimeOptions.defaultSerializer;
|
||||
return <String, dynamic>{
|
||||
'message_id': serializer.toJson<String>(messageId),
|
||||
'chat_id': serializer.toJson<String>(chatId),
|
||||
'order_key': serializer.toJson<String>(orderKey),
|
||||
'message_json': serializer.toJson<String>(messageJson),
|
||||
};
|
||||
}
|
||||
|
||||
ChatMessageData copyWith(
|
||||
{String? messageId,
|
||||
String? chatId,
|
||||
String? orderKey,
|
||||
String? messageJson}) =>
|
||||
ChatMessageData(
|
||||
messageId: messageId ?? this.messageId,
|
||||
chatId: chatId ?? this.chatId,
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
messageJson: messageJson ?? this.messageJson,
|
||||
);
|
||||
ChatMessageData copyWithCompanion(ChatMessageCompanion data) {
|
||||
return ChatMessageData(
|
||||
messageId: data.messageId.present ? data.messageId.value : this.messageId,
|
||||
chatId: data.chatId.present ? data.chatId.value : this.chatId,
|
||||
orderKey: data.orderKey.present ? data.orderKey.value : this.orderKey,
|
||||
messageJson:
|
||||
data.messageJson.present ? data.messageJson.value : this.messageJson,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ChatMessageData(')
|
||||
..write('messageId: $messageId, ')
|
||||
..write('chatId: $chatId, ')
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('messageJson: $messageJson')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(messageId, chatId, orderKey, messageJson);
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is ChatMessageData &&
|
||||
other.messageId == this.messageId &&
|
||||
other.chatId == this.chatId &&
|
||||
other.orderKey == this.orderKey &&
|
||||
other.messageJson == this.messageJson);
|
||||
}
|
||||
|
||||
class ChatMessageCompanion extends UpdateCompanion<ChatMessageData> {
|
||||
final Value<String> messageId;
|
||||
final Value<String> chatId;
|
||||
final Value<String> orderKey;
|
||||
final Value<String> messageJson;
|
||||
final Value<int> rowid;
|
||||
const ChatMessageCompanion({
|
||||
this.messageId = const Value.absent(),
|
||||
this.chatId = const Value.absent(),
|
||||
this.orderKey = const Value.absent(),
|
||||
this.messageJson = const Value.absent(),
|
||||
this.rowid = const Value.absent(),
|
||||
});
|
||||
ChatMessageCompanion.insert({
|
||||
required String messageId,
|
||||
required String chatId,
|
||||
required String orderKey,
|
||||
required String messageJson,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : messageId = Value(messageId),
|
||||
chatId = Value(chatId),
|
||||
orderKey = Value(orderKey),
|
||||
messageJson = Value(messageJson);
|
||||
static Insertable<ChatMessageData> custom({
|
||||
Expression<String>? messageId,
|
||||
Expression<String>? chatId,
|
||||
Expression<String>? orderKey,
|
||||
Expression<String>? messageJson,
|
||||
Expression<int>? rowid,
|
||||
}) {
|
||||
return RawValuesInsertable({
|
||||
if (messageId != null) 'message_id': messageId,
|
||||
if (chatId != null) 'chat_id': chatId,
|
||||
if (orderKey != null) 'order_key': orderKey,
|
||||
if (messageJson != null) 'message_json': messageJson,
|
||||
if (rowid != null) 'rowid': rowid,
|
||||
});
|
||||
}
|
||||
|
||||
ChatMessageCompanion copyWith(
|
||||
{Value<String>? messageId,
|
||||
Value<String>? chatId,
|
||||
Value<String>? orderKey,
|
||||
Value<String>? messageJson,
|
||||
Value<int>? rowid}) {
|
||||
return ChatMessageCompanion(
|
||||
messageId: messageId ?? this.messageId,
|
||||
chatId: chatId ?? this.chatId,
|
||||
orderKey: orderKey ?? this.orderKey,
|
||||
messageJson: messageJson ?? this.messageJson,
|
||||
rowid: rowid ?? this.rowid,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, Expression> toColumns(bool nullToAbsent) {
|
||||
final map = <String, Expression>{};
|
||||
if (messageId.present) {
|
||||
map['message_id'] = Variable<String>(messageId.value);
|
||||
}
|
||||
if (chatId.present) {
|
||||
map['chat_id'] = Variable<String>(chatId.value);
|
||||
}
|
||||
if (orderKey.present) {
|
||||
map['order_key'] = Variable<String>(orderKey.value);
|
||||
}
|
||||
if (messageJson.present) {
|
||||
map['message_json'] = Variable<String>(messageJson.value);
|
||||
}
|
||||
if (rowid.present) {
|
||||
map['rowid'] = Variable<int>(rowid.value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return (StringBuffer('ChatMessageCompanion(')
|
||||
..write('messageId: $messageId, ')
|
||||
..write('chatId: $chatId, ')
|
||||
..write('orderKey: $orderKey, ')
|
||||
..write('messageJson: $messageJson, ')
|
||||
..write('rowid: $rowid')
|
||||
..write(')'))
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$ChatDatabase extends GeneratedDatabase {
|
||||
_$ChatDatabase(QueryExecutor e) : super(e);
|
||||
$ChatDatabaseManager get managers => $ChatDatabaseManager(this);
|
||||
late final ChatData chatData = ChatData(this);
|
||||
late final ChatMessage chatMessage = ChatMessage(this);
|
||||
late final Index groupOrderIdx = Index('group_order_idx',
|
||||
'CREATE UNIQUE INDEX group_order_idx ON chat_message (chat_id, order_key)');
|
||||
late final MessagesDao messagesDao = MessagesDao(this as ChatDatabase);
|
||||
late final MetadataDao metadataDao = MetadataDao(this as ChatDatabase);
|
||||
Selectable<String> leadingOrderKey(
|
||||
{required int bucket, required String chatId}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_previous(?1, (SELECT order_key FROM chat_message WHERE chat_id = ?2 ORDER BY order_key LIMIT 1)) AS _c0',
|
||||
variables: [
|
||||
Variable<int>(bucket),
|
||||
Variable<String>(chatId)
|
||||
],
|
||||
readsFrom: {
|
||||
chatMessage,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
Selectable<String> trailingOrderKey(
|
||||
{required int bucket, required String chatId}) {
|
||||
return customSelect(
|
||||
'SELECT lexo_rank_next(?1, (SELECT order_key FROM chat_message WHERE chat_id = ?2 ORDER BY order_key DESC LIMIT 1)) AS _c0',
|
||||
variables: [
|
||||
Variable<int>(bucket),
|
||||
Variable<String>(chatId)
|
||||
],
|
||||
readsFrom: {
|
||||
chatMessage,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
Selectable<String> orderKeyBeforeIndex(
|
||||
{required String chatId, required int rowIdx}) {
|
||||
return customSelect(
|
||||
'WITH ordered_table AS (SELECT message_id, order_key, LEAD(order_key)OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS next_order_key, ROW_NUMBER()OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS row_idx, COUNT(*)OVER (RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS total_rows FROM chat_message WHERE chat_id = ?1) SELECT lexo_rank_reorder_after(order_key, next_order_key) AS _c0 FROM ordered_table WHERE row_idx = MIN(?2, total_rows)',
|
||||
variables: [
|
||||
Variable<String>(chatId),
|
||||
Variable<int>(rowIdx)
|
||||
],
|
||||
readsFrom: {
|
||||
chatMessage,
|
||||
}).map((QueryRow row) => row.read<String>('_c0'));
|
||||
}
|
||||
|
||||
Selectable<int> messageIndex(
|
||||
{required String chatId, required String messageId}) {
|
||||
return customSelect(
|
||||
'WITH ordered_table AS (SELECT message_id, ROW_NUMBER()OVER (ORDER BY order_key RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS row_idx FROM chat_message WHERE chat_id = ?1) SELECT row_idx FROM ordered_table WHERE message_id = ?2',
|
||||
variables: [
|
||||
Variable<String>(chatId),
|
||||
Variable<String>(messageId)
|
||||
],
|
||||
readsFrom: {
|
||||
chatMessage,
|
||||
}).map((QueryRow row) => row.read<int>('row_idx'));
|
||||
}
|
||||
|
||||
@override
|
||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||
@override
|
||||
List<DatabaseSchemaEntity> get allSchemaEntities =>
|
||||
[chatData, chatMessage, groupOrderIdx];
|
||||
}
|
||||
|
||||
typedef $ChatDataCreateCompanionBuilder = ChatDataCompanion Function({
|
||||
required String chatId,
|
||||
required ChatMetadata metadata,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $ChatDataUpdateCompanionBuilder = ChatDataCompanion Function({
|
||||
Value<String> chatId,
|
||||
Value<ChatMetadata> metadata,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
class $ChatDataFilterComposer extends Composer<_$ChatDatabase, ChatData> {
|
||||
$ChatDataFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get chatId => $composableBuilder(
|
||||
column: $table.chatId, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnWithTypeConverterFilters<ChatMetadata, ChatMetadata, String>
|
||||
get metadata => $composableBuilder(
|
||||
column: $table.metadata,
|
||||
builder: (column) => ColumnWithTypeConverterFilters(column));
|
||||
}
|
||||
|
||||
class $ChatDataOrderingComposer extends Composer<_$ChatDatabase, ChatData> {
|
||||
$ChatDataOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get chatId => $composableBuilder(
|
||||
column: $table.chatId, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get metadata => $composableBuilder(
|
||||
column: $table.metadata, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $ChatDataAnnotationComposer extends Composer<_$ChatDatabase, ChatData> {
|
||||
$ChatDataAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get chatId =>
|
||||
$composableBuilder(column: $table.chatId, builder: (column) => column);
|
||||
|
||||
GeneratedColumnWithTypeConverter<ChatMetadata, String> get metadata =>
|
||||
$composableBuilder(column: $table.metadata, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $ChatDataTableManager extends RootTableManager<
|
||||
_$ChatDatabase,
|
||||
ChatData,
|
||||
ChatDataData,
|
||||
$ChatDataFilterComposer,
|
||||
$ChatDataOrderingComposer,
|
||||
$ChatDataAnnotationComposer,
|
||||
$ChatDataCreateCompanionBuilder,
|
||||
$ChatDataUpdateCompanionBuilder,
|
||||
(ChatDataData, BaseReferences<_$ChatDatabase, ChatData, ChatDataData>),
|
||||
ChatDataData,
|
||||
PrefetchHooks Function()> {
|
||||
$ChatDataTableManager(_$ChatDatabase db, ChatData table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$ChatDataFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$ChatDataOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$ChatDataAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> chatId = const Value.absent(),
|
||||
Value<ChatMetadata> metadata = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ChatDataCompanion(
|
||||
chatId: chatId,
|
||||
metadata: metadata,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String chatId,
|
||||
required ChatMetadata metadata,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ChatDataCompanion.insert(
|
||||
chatId: chatId,
|
||||
metadata: metadata,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $ChatDataProcessedTableManager = ProcessedTableManager<
|
||||
_$ChatDatabase,
|
||||
ChatData,
|
||||
ChatDataData,
|
||||
$ChatDataFilterComposer,
|
||||
$ChatDataOrderingComposer,
|
||||
$ChatDataAnnotationComposer,
|
||||
$ChatDataCreateCompanionBuilder,
|
||||
$ChatDataUpdateCompanionBuilder,
|
||||
(ChatDataData, BaseReferences<_$ChatDatabase, ChatData, ChatDataData>),
|
||||
ChatDataData,
|
||||
PrefetchHooks Function()>;
|
||||
typedef $ChatMessageCreateCompanionBuilder = ChatMessageCompanion Function({
|
||||
required String messageId,
|
||||
required String chatId,
|
||||
required String orderKey,
|
||||
required String messageJson,
|
||||
Value<int> rowid,
|
||||
});
|
||||
typedef $ChatMessageUpdateCompanionBuilder = ChatMessageCompanion Function({
|
||||
Value<String> messageId,
|
||||
Value<String> chatId,
|
||||
Value<String> orderKey,
|
||||
Value<String> messageJson,
|
||||
Value<int> rowid,
|
||||
});
|
||||
|
||||
class $ChatMessageFilterComposer extends Composer<_$ChatDatabase, ChatMessage> {
|
||||
$ChatMessageFilterComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnFilters<String> get messageId => $composableBuilder(
|
||||
column: $table.messageId, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get chatId => $composableBuilder(
|
||||
column: $table.chatId, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get orderKey => $composableBuilder(
|
||||
column: $table.orderKey, builder: (column) => ColumnFilters(column));
|
||||
|
||||
ColumnFilters<String> get messageJson => $composableBuilder(
|
||||
column: $table.messageJson, builder: (column) => ColumnFilters(column));
|
||||
}
|
||||
|
||||
class $ChatMessageOrderingComposer
|
||||
extends Composer<_$ChatDatabase, ChatMessage> {
|
||||
$ChatMessageOrderingComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
ColumnOrderings<String> get messageId => $composableBuilder(
|
||||
column: $table.messageId, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get chatId => $composableBuilder(
|
||||
column: $table.chatId, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get orderKey => $composableBuilder(
|
||||
column: $table.orderKey, builder: (column) => ColumnOrderings(column));
|
||||
|
||||
ColumnOrderings<String> get messageJson => $composableBuilder(
|
||||
column: $table.messageJson, builder: (column) => ColumnOrderings(column));
|
||||
}
|
||||
|
||||
class $ChatMessageAnnotationComposer
|
||||
extends Composer<_$ChatDatabase, ChatMessage> {
|
||||
$ChatMessageAnnotationComposer({
|
||||
required super.$db,
|
||||
required super.$table,
|
||||
super.joinBuilder,
|
||||
super.$addJoinBuilderToRootComposer,
|
||||
super.$removeJoinBuilderFromRootComposer,
|
||||
});
|
||||
GeneratedColumn<String> get messageId =>
|
||||
$composableBuilder(column: $table.messageId, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get chatId =>
|
||||
$composableBuilder(column: $table.chatId, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get orderKey =>
|
||||
$composableBuilder(column: $table.orderKey, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get messageJson => $composableBuilder(
|
||||
column: $table.messageJson, builder: (column) => column);
|
||||
}
|
||||
|
||||
class $ChatMessageTableManager extends RootTableManager<
|
||||
_$ChatDatabase,
|
||||
ChatMessage,
|
||||
ChatMessageData,
|
||||
$ChatMessageFilterComposer,
|
||||
$ChatMessageOrderingComposer,
|
||||
$ChatMessageAnnotationComposer,
|
||||
$ChatMessageCreateCompanionBuilder,
|
||||
$ChatMessageUpdateCompanionBuilder,
|
||||
(
|
||||
ChatMessageData,
|
||||
BaseReferences<_$ChatDatabase, ChatMessage, ChatMessageData>
|
||||
),
|
||||
ChatMessageData,
|
||||
PrefetchHooks Function()> {
|
||||
$ChatMessageTableManager(_$ChatDatabase db, ChatMessage table)
|
||||
: super(TableManagerState(
|
||||
db: db,
|
||||
table: table,
|
||||
createFilteringComposer: () =>
|
||||
$ChatMessageFilterComposer($db: db, $table: table),
|
||||
createOrderingComposer: () =>
|
||||
$ChatMessageOrderingComposer($db: db, $table: table),
|
||||
createComputedFieldComposer: () =>
|
||||
$ChatMessageAnnotationComposer($db: db, $table: table),
|
||||
updateCompanionCallback: ({
|
||||
Value<String> messageId = const Value.absent(),
|
||||
Value<String> chatId = const Value.absent(),
|
||||
Value<String> orderKey = const Value.absent(),
|
||||
Value<String> messageJson = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ChatMessageCompanion(
|
||||
messageId: messageId,
|
||||
chatId: chatId,
|
||||
orderKey: orderKey,
|
||||
messageJson: messageJson,
|
||||
rowid: rowid,
|
||||
),
|
||||
createCompanionCallback: ({
|
||||
required String messageId,
|
||||
required String chatId,
|
||||
required String orderKey,
|
||||
required String messageJson,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) =>
|
||||
ChatMessageCompanion.insert(
|
||||
messageId: messageId,
|
||||
chatId: chatId,
|
||||
orderKey: orderKey,
|
||||
messageJson: messageJson,
|
||||
rowid: rowid,
|
||||
),
|
||||
withReferenceMapper: (p0) => p0
|
||||
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
|
||||
.toList(),
|
||||
prefetchHooksCallback: null,
|
||||
));
|
||||
}
|
||||
|
||||
typedef $ChatMessageProcessedTableManager = ProcessedTableManager<
|
||||
_$ChatDatabase,
|
||||
ChatMessage,
|
||||
ChatMessageData,
|
||||
$ChatMessageFilterComposer,
|
||||
$ChatMessageOrderingComposer,
|
||||
$ChatMessageAnnotationComposer,
|
||||
$ChatMessageCreateCompanionBuilder,
|
||||
$ChatMessageUpdateCompanionBuilder,
|
||||
(
|
||||
ChatMessageData,
|
||||
BaseReferences<_$ChatDatabase, ChatMessage, ChatMessageData>
|
||||
),
|
||||
ChatMessageData,
|
||||
PrefetchHooks Function()>;
|
||||
|
||||
class $ChatDatabaseManager {
|
||||
final _$ChatDatabase _db;
|
||||
$ChatDatabaseManager(this._db);
|
||||
$ChatDataTableManager get chatData =>
|
||||
$ChatDataTableManager(_db, _db.chatData);
|
||||
$ChatMessageTableManager get chatMessage =>
|
||||
$ChatMessageTableManager(_db, _db.chatMessage);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'chat_metadata.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ChatMetadata {
|
||||
final String? mainDocumentId;
|
||||
final String? contextId;
|
||||
|
||||
ChatMetadata({this.mainDocumentId, this.contextId});
|
||||
|
||||
factory ChatMetadata.fromJson(Map<String, dynamic> json) =>
|
||||
_$ChatMetadataFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$ChatMetadataToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_metadata.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ChatMetadata _$ChatMetadataFromJson(Map<String, dynamic> json) => ChatMetadata(
|
||||
mainDocumentId: json['mainDocumentId'] as String?,
|
||||
contextId: json['contextId'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ChatMetadataToJson(ChatMetadata instance) =>
|
||||
<String, dynamic>{
|
||||
'mainDocumentId': instance.mainDocumentId,
|
||||
'contextId': instance.contextId,
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
|
||||
enum MessageAuthor {
|
||||
///langchain SystemChatMessage
|
||||
system(User(id: 'system')),
|
||||
|
||||
///langchain HumanChatMessage
|
||||
human(User(id: 'human')),
|
||||
|
||||
///langchain AIChatMessage
|
||||
ai(User(id: 'ai')),
|
||||
|
||||
///langchain ToolChatMessage
|
||||
tool(User(id: 'tool')),
|
||||
|
||||
///langchain CustomChatMessage
|
||||
custom(User(id: 'custom'));
|
||||
|
||||
final User user;
|
||||
|
||||
const MessageAuthor(this.user);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:lensai/data/database/functions/lexo_rank_functions.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/database.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sqlite3/sqlite3.dart';
|
||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||
import 'package:universal_io/io.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
ChatDatabase chatDatabase(Ref ref) {
|
||||
final db = ChatDatabase(
|
||||
LazyDatabase(() async {
|
||||
// put the database file, called db.sqlite here, into the documents folder
|
||||
// for your app.
|
||||
final dbFolder = await path_provider.getApplicationDocumentsDirectory();
|
||||
final file = File(p.join(dbFolder.path, 'chat.db'));
|
||||
|
||||
// Also work around limitations on old Android versions
|
||||
if (Platform.isAndroid) {
|
||||
await applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
|
||||
}
|
||||
|
||||
// Make sqlite3 pick a more suitable location for temporary files - the
|
||||
// one from the system may be inaccessible due to sandboxing.
|
||||
final cachebase = (await path_provider.getTemporaryDirectory()).path;
|
||||
// We can't access /tmp on Android, which sqlite3 would try by default.
|
||||
// Explicitly tell it about the correct temporary directory.
|
||||
sqlite3.tempDirectory = cachebase;
|
||||
|
||||
return NativeDatabase.createInBackground(
|
||||
file,
|
||||
setup: (database) {
|
||||
registerLexorankFunctions(database);
|
||||
},
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
ref.onDispose(() {
|
||||
unawaited(db.close());
|
||||
});
|
||||
|
||||
return db;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatDatabaseHash() => r'b48955bc016f8c74684ed6e2c5cba99f5ab38dfe';
|
||||
|
||||
/// See also [chatDatabase].
|
||||
@ProviderFor(chatDatabase)
|
||||
final chatDatabaseProvider = Provider<ChatDatabase>.internal(
|
||||
chatDatabase,
|
||||
name: r'chatDatabaseProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product') ? null : _$chatDatabaseHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ChatDatabaseRef = ProviderRef<ChatDatabase>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,152 @@
|
||||
// ignore_for_file: avoid_dynamic_calls
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:langchain/langchain.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/message_types.dart';
|
||||
|
||||
class ChatMemory implements BaseMemory {
|
||||
final ChatController _controller;
|
||||
|
||||
/// Max number of tokens to use.
|
||||
final int maxTokenLimit;
|
||||
|
||||
/// Language model to use for counting tokens.
|
||||
final BaseLanguageModel llm;
|
||||
|
||||
/// The memory key to use for the chat history.
|
||||
/// This will be passed as input variable to the prompt.
|
||||
final String memoryKey;
|
||||
|
||||
/// If true, when [loadMemoryVariables] is called, it will return
|
||||
/// [ChatMessage] objects. If false, it will return a String representation
|
||||
/// of the messages.
|
||||
///
|
||||
/// Set this to true when you are using a Chat model like `ChatOpenAI`.
|
||||
/// Set this to false when you are use a text LLM like `OpenAI`.
|
||||
final bool returnMessages;
|
||||
|
||||
/// The prefix to use for system messages if [returnMessages] is false.
|
||||
final String systemPrefix;
|
||||
|
||||
/// The prefix to use for human messages if [returnMessages] is false.
|
||||
final String humanPrefix;
|
||||
|
||||
/// The prefix to use for AI messages if [returnMessages] is false.
|
||||
final String aiPrefix;
|
||||
|
||||
/// The prefix to use for tool messages if [returnMessages] is false.
|
||||
final String toolPrefix;
|
||||
|
||||
ChatMemory(
|
||||
this._controller, {
|
||||
required this.returnMessages,
|
||||
this.maxTokenLimit = 2000,
|
||||
required this.llm,
|
||||
this.memoryKey = BaseMemory.defaultMemoryKey,
|
||||
this.systemPrefix = SystemChatMessage.defaultPrefix,
|
||||
this.humanPrefix = HumanChatMessage.defaultPrefix,
|
||||
this.aiPrefix = AIChatMessage.defaultPrefix,
|
||||
this.toolPrefix = ToolChatMessage.defaultPrefix,
|
||||
});
|
||||
|
||||
@override
|
||||
Set<String> get memoryKeys => {memoryKey};
|
||||
|
||||
@override
|
||||
Future<MemoryVariables> loadMemoryVariables([
|
||||
MemoryInputValues values = const {},
|
||||
]) async {
|
||||
final messages = _controller.messages
|
||||
.whereNot(
|
||||
(message) => message.metadata?['hideFromModelChatHistory'] == true,
|
||||
)
|
||||
.map(
|
||||
(message) => switch (message) {
|
||||
TextMessage(
|
||||
authorId: final authorId,
|
||||
text: final text,
|
||||
metadata: final metadata,
|
||||
) =>
|
||||
() {
|
||||
if (authorId == MessageAuthor.system.user.id) {
|
||||
return SystemChatMessage(content: text);
|
||||
}
|
||||
if (authorId == MessageAuthor.human.user.id) {
|
||||
return HumanChatMessage(
|
||||
content: ChatMessageContent.text(text),
|
||||
);
|
||||
}
|
||||
if (authorId == MessageAuthor.ai.user.id) {
|
||||
final toolsRaw = metadata?['toolCalls'];
|
||||
if (toolsRaw is String) {
|
||||
return AIChatMessage(
|
||||
content: text,
|
||||
toolCalls: _deserializeToolCalls(toolsRaw),
|
||||
);
|
||||
}
|
||||
return AIChatMessage(content: text);
|
||||
}
|
||||
throw UnimplementedError();
|
||||
}(),
|
||||
ImageMessage() => throw UnimplementedError(),
|
||||
CustomMessage() => throw UnimplementedError(),
|
||||
UnsupportedMessage() => throw UnimplementedError()
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
|
||||
int currentBufferLength = await llm.countTokens(PromptValue.chat(messages));
|
||||
// Prune buffer if it exceeds max token limit
|
||||
if (currentBufferLength > maxTokenLimit) {
|
||||
while (currentBufferLength > maxTokenLimit) {
|
||||
//remove oldest entry
|
||||
messages.removeAt(0);
|
||||
currentBufferLength = await llm.countTokens(PromptValue.chat(messages));
|
||||
}
|
||||
}
|
||||
|
||||
if (returnMessages) {
|
||||
return {memoryKey: messages};
|
||||
}
|
||||
|
||||
return {
|
||||
memoryKey: messages.toBufferString(
|
||||
systemPrefix: systemPrefix,
|
||||
humanPrefix: humanPrefix,
|
||||
aiPrefix: aiPrefix,
|
||||
toolPrefix: toolPrefix,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
List<AIChatMessageToolCall> _deserializeToolCalls(String toolsRaw) {
|
||||
final decoded = jsonDecode(toolsRaw) as List<dynamic>;
|
||||
final tools = decoded
|
||||
.map(
|
||||
(tool) => AIChatMessageToolCall(
|
||||
id: tool['id'] as String,
|
||||
name: tool['name'] as String,
|
||||
arguments: tool['arguments'] as Map<String, dynamic>,
|
||||
argumentsRaw: tool['argumentsRaw'] as String,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return tools;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> saveContext({
|
||||
required MemoryInputValues inputValues,
|
||||
required MemoryOutputValues outputValues,
|
||||
}) {
|
||||
throw UnimplementedError('This is a read only memory');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
throw UnimplementedError('This is a read only memory');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/daos/messages.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
class DriftChatController implements ChatController {
|
||||
final MessagesDao _dao;
|
||||
final String chatId;
|
||||
|
||||
final _operationsController = StreamController<ChatOperation>.broadcast();
|
||||
|
||||
late final ValueStream<List<Message>> _mergedMessagesStream;
|
||||
late final StreamSubscription<List<Message>> _mergedMessagesStreamSub;
|
||||
late final BehaviorSubject<Map<String, Message>> _intermediateMessages;
|
||||
|
||||
DriftChatController(this._dao, {required this.chatId}) {
|
||||
final chatMessagesStream = _dao
|
||||
.chatMessages(chatId: chatId)
|
||||
.map(
|
||||
(data) => Message.fromJson(
|
||||
jsonDecode(data.messageJson) as Map<String, dynamic>,
|
||||
),
|
||||
)
|
||||
.watch();
|
||||
|
||||
_intermediateMessages = BehaviorSubject.seeded({});
|
||||
|
||||
_mergedMessagesStream = Rx.combineLatest2(
|
||||
chatMessagesStream,
|
||||
_intermediateMessages,
|
||||
(a, b) {
|
||||
if (a.isNotEmpty && b.isNotEmpty) {
|
||||
return [
|
||||
for (final message in a) b[message.id] ?? message,
|
||||
];
|
||||
}
|
||||
|
||||
return a;
|
||||
},
|
||||
).shareValue();
|
||||
|
||||
//Set initial messages
|
||||
unawaited(
|
||||
_mergedMessagesStream.first.then((messages) {
|
||||
if (messages.isNotEmpty) {
|
||||
_operationsController.add(ChatOperation.set());
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
_mergedMessagesStreamSub = _mergedMessagesStream.listen(null);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Message> get messages => _mergedMessagesStream.valueOrNull ?? [];
|
||||
|
||||
@override
|
||||
Future<void> insert(Message message, {int? index}) async {
|
||||
final insertedIndex = await _dao.insertMessage(
|
||||
chatId: chatId,
|
||||
message: message,
|
||||
index: index,
|
||||
);
|
||||
|
||||
if (insertedIndex != null) {
|
||||
await _mergedMessagesStream
|
||||
.firstWhere(
|
||||
(messages) =>
|
||||
messages.length > insertedIndex &&
|
||||
messages[insertedIndex].id == message.id,
|
||||
)
|
||||
.timeout(const Duration(milliseconds: 100));
|
||||
|
||||
_operationsController.add(ChatOperation.insert(message, insertedIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(Message message) async {
|
||||
final index = await _dao
|
||||
.messageIndex(chatId: chatId, messageId: message.id)
|
||||
.getSingleOrNull();
|
||||
|
||||
if (index != null) {
|
||||
await _dao.removeMessage(messageId: message.id);
|
||||
await _mergedMessagesStream
|
||||
.firstWhere(
|
||||
(messages) =>
|
||||
!messages.any((dbMessage) => dbMessage.id == message.id),
|
||||
)
|
||||
.timeout(const Duration(milliseconds: 100));
|
||||
|
||||
_operationsController.add(ChatOperation.remove(message, index));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> update(
|
||||
Message oldMessage,
|
||||
Message newMessage, {
|
||||
bool persistMessage = true,
|
||||
}) async {
|
||||
assert(oldMessage.id == newMessage.id);
|
||||
|
||||
if (persistMessage) {
|
||||
final intermediateMessage = _intermediateMessages.value[newMessage.id];
|
||||
|
||||
if (intermediateMessage == null && oldMessage == newMessage) {
|
||||
//nothing to update
|
||||
return;
|
||||
}
|
||||
|
||||
final rows = await _dao.updateMessage(
|
||||
messageId: oldMessage.id,
|
||||
message: newMessage,
|
||||
);
|
||||
|
||||
if (intermediateMessage != null) {
|
||||
_intermediateMessages.add(
|
||||
{..._intermediateMessages.value}..remove(newMessage.id),
|
||||
);
|
||||
}
|
||||
|
||||
if (rows <= 0 || intermediateMessage == newMessage) {
|
||||
//Dont send event
|
||||
return;
|
||||
}
|
||||
} else if (oldMessage != newMessage) {
|
||||
_intermediateMessages.add({
|
||||
..._intermediateMessages.value,
|
||||
newMessage.id: newMessage,
|
||||
});
|
||||
}
|
||||
|
||||
await _mergedMessagesStream
|
||||
.firstWhere(
|
||||
(messages) => messages.any((message) => message == newMessage),
|
||||
)
|
||||
.timeout(const Duration(milliseconds: 100));
|
||||
|
||||
_operationsController.add(ChatOperation.update(oldMessage, newMessage));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> set(List<Message> messages) async {
|
||||
await _dao.replaceChatMessages(chatId: chatId, messages: messages);
|
||||
await _mergedMessagesStream
|
||||
.firstWhere(
|
||||
(replacedMessages) =>
|
||||
const DeepCollectionEquality().equals(messages, replacedMessages),
|
||||
)
|
||||
.timeout(const Duration(milliseconds: 100));
|
||||
|
||||
_operationsController.add(ChatOperation.set());
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<ChatOperation> get operationsStream => _operationsController.stream;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
unawaited(_intermediateMessages.close());
|
||||
unawaited(_mergedMessagesStreamSub.cancel());
|
||||
unawaited(_operationsController.close());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/providers.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/domain/drift_chat_controller.dart';
|
||||
import 'package:riverpod/riverpod.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'providers.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
DriftChatController chatController(Ref ref, String chatId) {
|
||||
final db = ref.watch(chatDatabaseProvider);
|
||||
final controller = DriftChatController(db.messagesDao, chatId: chatId);
|
||||
|
||||
ref.onDispose(() {
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
@Riverpod()
|
||||
Stream<ChatMetadata?> chatMetadata(Ref ref, String chatId) {
|
||||
final db = ref.watch(chatDatabaseProvider);
|
||||
return db.metadataDao.chatMetadata(chatId).watchSingleOrNull();
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'providers.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatControllerHash() => r'493264a4413ffc2d767d73a90634e1a1744cfcfd';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
/// See also [chatController].
|
||||
@ProviderFor(chatController)
|
||||
const chatControllerProvider = ChatControllerFamily();
|
||||
|
||||
/// See also [chatController].
|
||||
class ChatControllerFamily extends Family<DriftChatController> {
|
||||
/// See also [chatController].
|
||||
const ChatControllerFamily();
|
||||
|
||||
/// See also [chatController].
|
||||
ChatControllerProvider call(
|
||||
String chatId,
|
||||
) {
|
||||
return ChatControllerProvider(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatControllerProvider getProviderOverride(
|
||||
covariant ChatControllerProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.chatId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'chatControllerProvider';
|
||||
}
|
||||
|
||||
/// See also [chatController].
|
||||
class ChatControllerProvider extends AutoDisposeProvider<DriftChatController> {
|
||||
/// See also [chatController].
|
||||
ChatControllerProvider(
|
||||
String chatId,
|
||||
) : this._internal(
|
||||
(ref) => chatController(
|
||||
ref as ChatControllerRef,
|
||||
chatId,
|
||||
),
|
||||
from: chatControllerProvider,
|
||||
name: r'chatControllerProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$chatControllerHash,
|
||||
dependencies: ChatControllerFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ChatControllerFamily._allTransitiveDependencies,
|
||||
chatId: chatId,
|
||||
);
|
||||
|
||||
ChatControllerProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.chatId,
|
||||
}) : super.internal();
|
||||
|
||||
final String chatId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
DriftChatController Function(ChatControllerRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ChatControllerProvider._internal(
|
||||
(ref) => create(ref as ChatControllerRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
chatId: chatId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeProviderElement<DriftChatController> createElement() {
|
||||
return _ChatControllerProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ChatControllerProvider && other.chatId == chatId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, chatId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ChatControllerRef on AutoDisposeProviderRef<DriftChatController> {
|
||||
/// The parameter `chatId` of this provider.
|
||||
String get chatId;
|
||||
}
|
||||
|
||||
class _ChatControllerProviderElement
|
||||
extends AutoDisposeProviderElement<DriftChatController>
|
||||
with ChatControllerRef {
|
||||
_ChatControllerProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get chatId => (origin as ChatControllerProvider).chatId;
|
||||
}
|
||||
|
||||
String _$chatMetadataHash() => r'22252e4a4c4e9cb039c5eee0e0f6eb5b8bd03944';
|
||||
|
||||
/// See also [chatMetadata].
|
||||
@ProviderFor(chatMetadata)
|
||||
const chatMetadataProvider = ChatMetadataFamily();
|
||||
|
||||
/// See also [chatMetadata].
|
||||
class ChatMetadataFamily extends Family<AsyncValue<ChatMetadata?>> {
|
||||
/// See also [chatMetadata].
|
||||
const ChatMetadataFamily();
|
||||
|
||||
/// See also [chatMetadata].
|
||||
ChatMetadataProvider call(
|
||||
String chatId,
|
||||
) {
|
||||
return ChatMetadataProvider(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatMetadataProvider getProviderOverride(
|
||||
covariant ChatMetadataProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.chatId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'chatMetadataProvider';
|
||||
}
|
||||
|
||||
/// See also [chatMetadata].
|
||||
class ChatMetadataProvider extends AutoDisposeStreamProvider<ChatMetadata?> {
|
||||
/// See also [chatMetadata].
|
||||
ChatMetadataProvider(
|
||||
String chatId,
|
||||
) : this._internal(
|
||||
(ref) => chatMetadata(
|
||||
ref as ChatMetadataRef,
|
||||
chatId,
|
||||
),
|
||||
from: chatMetadataProvider,
|
||||
name: r'chatMetadataProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$chatMetadataHash,
|
||||
dependencies: ChatMetadataFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ChatMetadataFamily._allTransitiveDependencies,
|
||||
chatId: chatId,
|
||||
);
|
||||
|
||||
ChatMetadataProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.chatId,
|
||||
}) : super.internal();
|
||||
|
||||
final String chatId;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
Stream<ChatMetadata?> Function(ChatMetadataRef provider) create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ChatMetadataProvider._internal(
|
||||
(ref) => create(ref as ChatMetadataRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
chatId: chatId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeStreamProviderElement<ChatMetadata?> createElement() {
|
||||
return _ChatMetadataProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ChatMetadataProvider && other.chatId == chatId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, chatId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ChatMetadataRef on AutoDisposeStreamProviderRef<ChatMetadata?> {
|
||||
/// The parameter `chatId` of this provider.
|
||||
String get chatId;
|
||||
}
|
||||
|
||||
class _ChatMetadataProviderElement
|
||||
extends AutoDisposeStreamProviderElement<ChatMetadata?>
|
||||
with ChatMetadataRef {
|
||||
_ChatMetadataProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get chatId => (origin as ChatMetadataProvider).chatId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
||||
import 'package:langchain/langchain.dart';
|
||||
import 'package:lensai/core/uuid.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/message_types.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/domain/drift_chat_controller.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/domain/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'chat_message.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class ChatMessageRepository extends _$ChatMessageRepository {
|
||||
late DriftChatController _controller;
|
||||
|
||||
final _isTyping = <MessageAuthor>{};
|
||||
|
||||
bool isTyping(MessageAuthor author) => _isTyping.contains(author);
|
||||
|
||||
Future<void> setTyping({
|
||||
required MessageAuthor author,
|
||||
required bool typing,
|
||||
}) async {
|
||||
if (isTyping(author) == typing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typing) {
|
||||
await _controller.insert(
|
||||
CustomMessage(
|
||||
id: uuid.v4(),
|
||||
authorId: author.user.id,
|
||||
createdAt: DateTime.now().toUtc(),
|
||||
metadata: {
|
||||
'type': 'typing',
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
_isTyping.add(author);
|
||||
} else {
|
||||
final typingMessages = _controller.messages.where(
|
||||
(message) =>
|
||||
message.authorId == author.user.id &&
|
||||
message.metadata?['type'] == 'typing',
|
||||
);
|
||||
|
||||
await Future.wait(
|
||||
typingMessages.map((message) => _controller.remove(message)),
|
||||
);
|
||||
|
||||
_isTyping.remove(author);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Result<TextMessage>> insertTextMessage({
|
||||
required MessageAuthor author,
|
||||
required String content,
|
||||
List<AIChatMessageToolCall>? toolCalls,
|
||||
bool hideFromModelChatHistory = false,
|
||||
}) async {
|
||||
final message = TextMessage(
|
||||
id: uuid.v4(),
|
||||
authorId: author.user.id,
|
||||
createdAt: DateTime.now().toUtc(),
|
||||
text: content,
|
||||
isOnlyEmoji: isOnlyEmoji(content),
|
||||
metadata: (hideFromModelChatHistory || (toolCalls?.isNotEmpty ?? false))
|
||||
? {
|
||||
'toolCalls': toolCalls?.map((tool) => tool.toMap()).toList(),
|
||||
if (hideFromModelChatHistory) 'hideFromModelChatHistory': true,
|
||||
}
|
||||
: null,
|
||||
);
|
||||
|
||||
return Result.fromAsync(() async {
|
||||
await _controller.insert(message);
|
||||
return message;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void build(String chatId) {
|
||||
_controller = ref.watch(chatControllerProvider(chatId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_message.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatMessageRepositoryHash() =>
|
||||
r'a4144618b339f7c0c329a01a294b6bebbb648bf6';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$ChatMessageRepository
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
late final String chatId;
|
||||
|
||||
void build(
|
||||
String chatId,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [ChatMessageRepository].
|
||||
@ProviderFor(ChatMessageRepository)
|
||||
const chatMessageRepositoryProvider = ChatMessageRepositoryFamily();
|
||||
|
||||
/// See also [ChatMessageRepository].
|
||||
class ChatMessageRepositoryFamily extends Family<void> {
|
||||
/// See also [ChatMessageRepository].
|
||||
const ChatMessageRepositoryFamily();
|
||||
|
||||
/// See also [ChatMessageRepository].
|
||||
ChatMessageRepositoryProvider call(
|
||||
String chatId,
|
||||
) {
|
||||
return ChatMessageRepositoryProvider(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatMessageRepositoryProvider getProviderOverride(
|
||||
covariant ChatMessageRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.chatId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'chatMessageRepositoryProvider';
|
||||
}
|
||||
|
||||
/// See also [ChatMessageRepository].
|
||||
class ChatMessageRepositoryProvider
|
||||
extends AutoDisposeNotifierProviderImpl<ChatMessageRepository, void> {
|
||||
/// See also [ChatMessageRepository].
|
||||
ChatMessageRepositoryProvider(
|
||||
String chatId,
|
||||
) : this._internal(
|
||||
() => ChatMessageRepository()..chatId = chatId,
|
||||
from: chatMessageRepositoryProvider,
|
||||
name: r'chatMessageRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$chatMessageRepositoryHash,
|
||||
dependencies: ChatMessageRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ChatMessageRepositoryFamily._allTransitiveDependencies,
|
||||
chatId: chatId,
|
||||
);
|
||||
|
||||
ChatMessageRepositoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.chatId,
|
||||
}) : super.internal();
|
||||
|
||||
final String chatId;
|
||||
|
||||
@override
|
||||
void runNotifierBuild(
|
||||
covariant ChatMessageRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(ChatMessageRepository Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ChatMessageRepositoryProvider._internal(
|
||||
() => create()..chatId = chatId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
chatId: chatId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<ChatMessageRepository, void>
|
||||
createElement() {
|
||||
return _ChatMessageRepositoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ChatMessageRepositoryProvider && other.chatId == chatId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, chatId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ChatMessageRepositoryRef on AutoDisposeNotifierProviderRef<void> {
|
||||
/// The parameter `chatId` of this provider.
|
||||
String get chatId;
|
||||
}
|
||||
|
||||
class _ChatMessageRepositoryProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<ChatMessageRepository, void>
|
||||
with ChatMessageRepositoryRef {
|
||||
_ChatMessageRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get chatId => (origin as ChatMessageRepositoryProvider).chatId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:exceptions/exceptions.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/database/database.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
||||
import 'package:lensai/features/chat/features/chat_store/data/providers.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
part 'chat_metadata.g.dart';
|
||||
|
||||
@Riverpod()
|
||||
class ChatMetadataRepository extends _$ChatMetadataRepository {
|
||||
late ChatDatabase _db;
|
||||
|
||||
Future<Result<void>> updateMetadata(ChatMetadata metadata) {
|
||||
return Result.fromAsync(() {
|
||||
return _db.metadataDao.updateChatMetadata(chatId, metadata);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void build(String chatId) {
|
||||
_db = ref.watch(chatDatabaseProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'chat_metadata.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$chatMetadataRepositoryHash() =>
|
||||
r'72a0226ed7e87f452f4dfe44594bb30663c2db45';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$ChatMetadataRepository
|
||||
extends BuildlessAutoDisposeNotifier<void> {
|
||||
late final String chatId;
|
||||
|
||||
void build(
|
||||
String chatId,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [ChatMetadataRepository].
|
||||
@ProviderFor(ChatMetadataRepository)
|
||||
const chatMetadataRepositoryProvider = ChatMetadataRepositoryFamily();
|
||||
|
||||
/// See also [ChatMetadataRepository].
|
||||
class ChatMetadataRepositoryFamily extends Family<void> {
|
||||
/// See also [ChatMetadataRepository].
|
||||
const ChatMetadataRepositoryFamily();
|
||||
|
||||
/// See also [ChatMetadataRepository].
|
||||
ChatMetadataRepositoryProvider call(
|
||||
String chatId,
|
||||
) {
|
||||
return ChatMetadataRepositoryProvider(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
ChatMetadataRepositoryProvider getProviderOverride(
|
||||
covariant ChatMetadataRepositoryProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.chatId,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'chatMetadataRepositoryProvider';
|
||||
}
|
||||
|
||||
/// See also [ChatMetadataRepository].
|
||||
class ChatMetadataRepositoryProvider
|
||||
extends AutoDisposeNotifierProviderImpl<ChatMetadataRepository, void> {
|
||||
/// See also [ChatMetadataRepository].
|
||||
ChatMetadataRepositoryProvider(
|
||||
String chatId,
|
||||
) : this._internal(
|
||||
() => ChatMetadataRepository()..chatId = chatId,
|
||||
from: chatMetadataRepositoryProvider,
|
||||
name: r'chatMetadataRepositoryProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$chatMetadataRepositoryHash,
|
||||
dependencies: ChatMetadataRepositoryFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ChatMetadataRepositoryFamily._allTransitiveDependencies,
|
||||
chatId: chatId,
|
||||
);
|
||||
|
||||
ChatMetadataRepositoryProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.chatId,
|
||||
}) : super.internal();
|
||||
|
||||
final String chatId;
|
||||
|
||||
@override
|
||||
void runNotifierBuild(
|
||||
covariant ChatMetadataRepository notifier,
|
||||
) {
|
||||
return notifier.build(
|
||||
chatId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(ChatMetadataRepository Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ChatMetadataRepositoryProvider._internal(
|
||||
() => create()..chatId = chatId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
chatId: chatId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<ChatMetadataRepository, void>
|
||||
createElement() {
|
||||
return _ChatMetadataRepositoryProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ChatMetadataRepositoryProvider && other.chatId == chatId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, chatId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ChatMetadataRepositoryRef on AutoDisposeNotifierProviderRef<void> {
|
||||
/// The parameter `chatId` of this provider.
|
||||
String get chatId;
|
||||
}
|
||||
|
||||
class _ChatMetadataRepositoryProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<ChatMetadataRepository, void>
|
||||
with ChatMetadataRepositoryRef {
|
||||
_ChatMetadataRepositoryProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get chatId => (origin as ChatMetadataRepositoryProvider).chatId;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
Reference in New Issue
Block a user