strip chat
This commit is contained in:
@@ -1,40 +0,0 @@
|
|||||||
import 'package:langchain_openai/langchain_openai.dart';
|
|
||||||
import 'package:riverpod/riverpod.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
|
|
||||||
part 'models.g.dart';
|
|
||||||
|
|
||||||
// @Riverpod(keepAlive: true)
|
|
||||||
// ChatOpenAI summarizerModel(Ref ref) {
|
|
||||||
// final openaiApiKey = Platform.environment['OPENAI_API_KEY'];
|
|
||||||
// return ChatOpenAI(
|
|
||||||
// apiKey: openaiApiKey,
|
|
||||||
// baseUrl: 'https://api.together.xyz/v1',
|
|
||||||
// defaultOptions: const ChatOpenAIOptions(model: 'deepseek-ai/DeepSeek-V3'),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
ChatOpenAI chatModel(Ref ref) {
|
|
||||||
const openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
|
||||||
return ChatOpenAI(
|
|
||||||
apiKey: openaiApiKey,
|
|
||||||
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
|
||||||
defaultOptions: const ChatOpenAIOptions(
|
|
||||||
model: 'meta-llama/Llama-3.3-70B-Instruct-Turbo',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
OpenAIEmbeddings embeddingModel(Ref ref) {
|
|
||||||
const openaiApiKey = 'FfTBKW1t5dOOXVoiaF2hbXPAHiz3fvfy';
|
|
||||||
return OpenAIEmbeddings(
|
|
||||||
apiKey: openaiApiKey,
|
|
||||||
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
|
||||||
model: 'BAAI/bge-m3',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
|
||||||
int embeddingDimensions(Ref ref) => 1024;
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'models.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$chatModelHash() => r'9e44e8fa1d688968841933aac8e13212cf41f4ed';
|
|
||||||
|
|
||||||
/// See also [chatModel].
|
|
||||||
@ProviderFor(chatModel)
|
|
||||||
final chatModelProvider = Provider<ChatOpenAI>.internal(
|
|
||||||
chatModel,
|
|
||||||
name: r'chatModelProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product') ? null : _$chatModelHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
typedef ChatModelRef = ProviderRef<ChatOpenAI>;
|
|
||||||
String _$embeddingModelHash() => r'40111a5f08b43afe6edd399ff0e6c022b3387265';
|
|
||||||
|
|
||||||
/// See also [embeddingModel].
|
|
||||||
@ProviderFor(embeddingModel)
|
|
||||||
final embeddingModelProvider = Provider<OpenAIEmbeddings>.internal(
|
|
||||||
embeddingModel,
|
|
||||||
name: r'embeddingModelProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$embeddingModelHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
typedef EmbeddingModelRef = ProviderRef<OpenAIEmbeddings>;
|
|
||||||
String _$embeddingDimensionsHash() =>
|
|
||||||
r'e34f544a18a7e2bd6a479b21f2016b0c0e2ba96c';
|
|
||||||
|
|
||||||
/// See also [embeddingDimensions].
|
|
||||||
@ProviderFor(embeddingDimensions)
|
|
||||||
final embeddingDimensionsProvider = Provider<int>.internal(
|
|
||||||
embeddingDimensions,
|
|
||||||
name: r'embeddingDimensionsProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$embeddingDimensionsHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
typedef EmbeddingDimensionsRef = ProviderRef<int>;
|
|
||||||
// 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
|
|
||||||
-18
@@ -1,18 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:lexo_rank/lexo_rank.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/database.dart';
|
|
||||||
|
|
||||||
part 'messages.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor()
|
|
||||||
class MessagesDao extends DatabaseAccessor<ChatDatabase>
|
|
||||||
with _$MessagesDaoMixin {
|
|
||||||
MessagesDao(super.db);
|
|
||||||
|
|
||||||
Selectable<int> getMessageCount({required String chatId}) {
|
|
||||||
return db.chatMessage.count(where: (row) => row.chatId.equals(chatId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Selectable<ChatMessageData> getChatMessages({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> getMessageIndex({
|
|
||||||
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,
|
|
||||||
}) {
|
|
||||||
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;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'messages.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$MessagesDaoMixin on DatabaseAccessor<ChatDatabase> {}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/database.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
|
|
||||||
part 'metadata.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor()
|
|
||||||
class MetadataDao extends DatabaseAccessor<ChatDatabase>
|
|
||||||
with _$MetadataDaoMixin {
|
|
||||||
MetadataDao(super.db);
|
|
||||||
|
|
||||||
SingleOrNullSelectable<ChatMetadata> getChatMetadata(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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'metadata.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$MetadataDaoMixin on DatabaseAccessor<ChatDatabase> {}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/converters/chat_metadata_converter.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/daos/messages.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/daos/metadata.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
|
|
||||||
part 'database.g.dart';
|
|
||||||
|
|
||||||
@DriftDatabase(include: {'database.drift'}, daos: [MessagesDao, MetadataDao])
|
|
||||||
class ChatDatabase extends _$ChatDatabase {
|
|
||||||
ChatDatabase(super.e);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get schemaVersion => 1;
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import 'package:weblibre/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;
|
|
||||||
@@ -1,871 +0,0 @@
|
|||||||
// 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);
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:fast_equatable/fast_equatable.dart';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'chat_metadata.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class ChatMetadata with FastEquatable {
|
|
||||||
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);
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get hashParameters => [mainDocumentId, contextId];
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
// 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,
|
|
||||||
};
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
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';
|
|
||||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/database.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(() async {
|
|
||||||
await db.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'providers.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$chatDatabaseHash() => r'd07fef426c6984e6fb2eab9651a5c51aaca98961';
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
// ignore_for_file: avoid_dynamic_calls we know what we do
|
|
||||||
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:weblibre/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');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-166
@@ -1,166 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/database/daos/messages.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
|
|
||||||
.getChatMessages(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
|
|
||||||
.getMessageIndex(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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import 'package:riverpod/riverpod.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/providers.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/controllers/drift_chat_controller.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.getChatMetadata(chatId).watchSingleOrNull();
|
|
||||||
}
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
// 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'e50f492ae69168c9f9c4df89e29424b72b681e27';
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import 'package:exceptions/exceptions.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:nullability/nullability.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/core/uuid.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/message_types.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/controllers/drift_chat_controller.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/providers.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)).toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
_isTyping.remove(author);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result<TextMessage>> insertTextMessage({
|
|
||||||
required MessageAuthor author,
|
|
||||||
required String content,
|
|
||||||
List<AIChatMessageToolCall>? toolCalls,
|
|
||||||
bool hideFromModelChatHistory = false,
|
|
||||||
}) {
|
|
||||||
final message = TextMessage(
|
|
||||||
id: uuid.v4(),
|
|
||||||
authorId: author.user.id,
|
|
||||||
createdAt: DateTime.now().toUtc(),
|
|
||||||
text: content,
|
|
||||||
isOnlyEmoji: isOnlyEmoji(content),
|
|
||||||
metadata:
|
|
||||||
(hideFromModelChatHistory || (toolCalls.isNotEmpty))
|
|
||||||
? {
|
|
||||||
'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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'chat_message.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$chatMessageRepositoryHash() =>
|
|
||||||
r'34055588fb83fd3d4a91219e96a0c5e961af5212';
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import 'package:exceptions/exceptions.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/providers.dart';
|
|
||||||
|
|
||||||
part 'chat_metadata.g.dart';
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class ChatMetadataRepository extends _$ChatMetadataRepository {
|
|
||||||
Future<Result<void>> updateMetadata(ChatMetadata metadata) {
|
|
||||||
return Result.fromAsync(() {
|
|
||||||
return ref
|
|
||||||
.read(chatDatabaseProvider)
|
|
||||||
.metadataDao
|
|
||||||
.updateChatMetadata(chatId, metadata);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void build(String chatId) {}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'chat_metadata.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$chatMetadataRepositoryHash() =>
|
|
||||||
r'bd6b28b9d034ee82f95e974e4bf8f553546e835a';
|
|
||||||
|
|
||||||
/// 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
|
|
||||||
@@ -10,13 +10,8 @@ import 'package:share_plus/share_plus.dart';
|
|||||||
import 'package:weblibre/data/models/web_page_info.dart';
|
import 'package:weblibre/data/models/web_page_info.dart';
|
||||||
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
import 'package:weblibre/features/bangs/domain/providers/bangs.dart';
|
||||||
import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart';
|
import 'package:weblibre/features/bangs/presentation/widgets/site_search.dart';
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/repositories/chat_metadata.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/controllers/bottom_sheet.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
import 'package:weblibre/features/geckoview/domain/providers/tab_session.dart';
|
||||||
import 'package:weblibre/features/geckoview/domain/providers/tab_state.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
import 'package:weblibre/features/geckoview/domain/repositories/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/entities/sheet.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart';
|
import 'package:weblibre/features/geckoview/features/browser/presentation/widgets/browser_modules/address_with_suggestions_field.dart';
|
||||||
import 'package:weblibre/features/user/domain/providers.dart';
|
import 'package:weblibre/features/user/domain/providers.dart';
|
||||||
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
import 'package:weblibre/presentation/widgets/failure_widget.dart';
|
||||||
@@ -236,59 +231,6 @@ class WebPageDialog extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
|
||||||
ListTile(
|
|
||||||
leading: const Icon(MdiIcons.brain),
|
|
||||||
title: const Text('QA Chat'),
|
|
||||||
onTap: () async {
|
|
||||||
final selectedTabId =
|
|
||||||
ref.read(selectedTabStateProvider)?.id;
|
|
||||||
|
|
||||||
if (selectedTabId != null) {
|
|
||||||
final updateResult = await ref
|
|
||||||
.read(
|
|
||||||
chatMetadataRepositoryProvider(
|
|
||||||
selectedTabId,
|
|
||||||
).notifier,
|
|
||||||
)
|
|
||||||
.updateMetadata(
|
|
||||||
ChatMetadata(mainDocumentId: selectedTabId),
|
|
||||||
);
|
|
||||||
|
|
||||||
updateResult.onSuccess((_) {
|
|
||||||
ref
|
|
||||||
.read(
|
|
||||||
bottomSheetControllerProvider.notifier,
|
|
||||||
)
|
|
||||||
.show(
|
|
||||||
TabQaChatSheet(chatId: selectedTabId),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.mounted) {
|
|
||||||
context.pop();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
// ListTile(
|
|
||||||
// leading: Icon(MdiIcons.text),
|
|
||||||
// title: const Text('Summarize'),
|
|
||||||
// onTap: () async {
|
|
||||||
// final summarizerUrl = uri_builder.summarizerUri(
|
|
||||||
// document: SharedUrl(url),
|
|
||||||
// mode: SummarizerMode.keyMoments,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// await ref
|
|
||||||
// .read(tabRepositoryProvider.notifier)
|
|
||||||
// .addTab(url: summarizerUrl);
|
|
||||||
|
|
||||||
// if (context.mounted) {
|
|
||||||
// context.pop();
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import 'package:weblibre/features/geckoview/features/browser/presentation/widget
|
|||||||
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
import 'package:weblibre/features/geckoview/features/contextmenu/extensions/hit_result.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
|
import 'package:weblibre/features/geckoview/features/find_in_page/presentation/widgets/find_in_page.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
|
import 'package:weblibre/features/geckoview/features/readerview/presentation/widgets/reader_appearance_button.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/chat/presentation/widgets/tab_qa_chat.dart';
|
|
||||||
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
import 'package:weblibre/utils/ui_helper.dart' as ui_helper;
|
||||||
|
|
||||||
class BrowserScreen extends HookConsumerWidget {
|
class BrowserScreen extends HookConsumerWidget {
|
||||||
@@ -371,12 +370,6 @@ class _QaSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
|
||||||
child: TabQaChat(
|
|
||||||
chatId: chatId,
|
|
||||||
scrollController: scrollController,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
-2
@@ -19,7 +19,6 @@ import 'package:weblibre/features/geckoview/features/browser/domain/providers/li
|
|||||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/services/browser_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/services/engine_settings_replication.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart';
|
import 'package:weblibre/features/geckoview/features/browser/domain/services/proxy_settings_replication.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
|
||||||
import 'package:weblibre/features/user/domain/repositories/cache.dart';
|
import 'package:weblibre/features/user/domain/repositories/cache.dart';
|
||||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||||
import 'package:weblibre/features/user/domain/services/local_authentication.dart';
|
import 'package:weblibre/features/user/domain/services/local_authentication.dart';
|
||||||
@@ -154,7 +153,6 @@ class _BrowserViewState extends ConsumerState<BrowserView>
|
|||||||
|
|
||||||
//Initialize and register dependencies
|
//Initialize and register dependencies
|
||||||
ref.listenManual(tabRepositoryProvider, (previous, next) {});
|
ref.listenManual(tabRepositoryProvider, (previous, next) {});
|
||||||
ref.listenManual(documentRepositoryProvider, (previous, next) {});
|
|
||||||
|
|
||||||
ref.listenManual(selectionActionServiceProvider, (previous, next) {});
|
ref.listenManual(selectionActionServiceProvider, (previous, next) {});
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,12 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/con
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/tab.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/tab.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/database/migrator.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
|
|
||||||
import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
import 'package:weblibre/features/search/domain/fts_tokenizer.dart';
|
||||||
|
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
||||||
@DriftDatabase(
|
@DriftDatabase(include: {'database.drift'}, daos: [ContainerDao, TabDao])
|
||||||
include: {'database.drift'},
|
|
||||||
daos: [ContainerDao, TabDao, VectorDao],
|
|
||||||
)
|
|
||||||
class TabDatabase extends _$TabDatabase with TrigramQueryBuilderMixin {
|
class TabDatabase extends _$TabDatabase with TrigramQueryBuilderMixin {
|
||||||
final int embeddingDimensions;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final int schemaVersion = 2;
|
final int schemaVersion = 2;
|
||||||
|
|
||||||
@@ -31,23 +23,11 @@ class TabDatabase extends _$TabDatabase with TrigramQueryBuilderMixin {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
onCreate: (m) async {
|
|
||||||
final migrator = VectorDatabaseMigrator(dimensions: embeddingDimensions);
|
|
||||||
|
|
||||||
await m.database.customStatement(migrator.vectorTableDefinition);
|
|
||||||
|
|
||||||
//instead of m.createAll(); we igoner vec0 table
|
|
||||||
for (final entity in allSchemaEntities.where(
|
|
||||||
(entity) => entity is! DocumentVec,
|
|
||||||
)) {
|
|
||||||
await m.create(entity);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeOpen: (details) async {
|
beforeOpen: (details) async {
|
||||||
await customStatement('PRAGMA foreign_keys = ON;');
|
await customStatement('PRAGMA foreign_keys = ON;');
|
||||||
await optimizeFtsIndex();
|
await optimizeFtsIndex();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
TabDatabase(super.e, {required this.embeddingDimensions});
|
TabDatabase(super.e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,6 @@ CREATE TABLE tab (
|
|||||||
timestamp DATETIME NOT NULL
|
timestamp DATETIME NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
import '../../features/vector_store/data/database/vector_store.drift';
|
|
||||||
|
|
||||||
-- automativcally remove documents and embeddings on delete
|
|
||||||
-- isnert and updates are managed code side
|
|
||||||
CREATE TRIGGER tab_document_delete AFTER DELETE ON tab BEGIN
|
|
||||||
DELETE FROM document WHERE main_document_id = old.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE VIRTUAL TABLE tab_fts
|
CREATE VIRTUAL TABLE tab_fts
|
||||||
USING fts5(
|
USING fts5(
|
||||||
title,
|
title,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,7 @@ import 'package:riverpod/riverpod.dart';
|
|||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:sqlite3/sqlite3.dart';
|
import 'package:sqlite3/sqlite3.dart';
|
||||||
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart';
|
||||||
import 'package:sqlite3_vec/sqlite3_vec.dart';
|
|
||||||
import 'package:universal_io/io.dart';
|
import 'package:universal_io/io.dart';
|
||||||
import 'package:weblibre/core/providers/models.dart';
|
|
||||||
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
import 'package:weblibre/data/database/functions/lexo_rank_functions.dart';
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
||||||
|
|
||||||
@@ -16,8 +14,6 @@ part 'providers.g.dart';
|
|||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
TabDatabase tabDatabase(Ref ref) {
|
TabDatabase tabDatabase(Ref ref) {
|
||||||
final dimensions = ref.watch(embeddingDimensionsProvider);
|
|
||||||
|
|
||||||
final db = TabDatabase(
|
final db = TabDatabase(
|
||||||
LazyDatabase(() async {
|
LazyDatabase(() async {
|
||||||
// put the database file, called db.sqlite here, into the documents folder
|
// put the database file, called db.sqlite here, into the documents folder
|
||||||
@@ -37,8 +33,6 @@ TabDatabase tabDatabase(Ref ref) {
|
|||||||
// Explicitly tell it about the correct temporary directory.
|
// Explicitly tell it about the correct temporary directory.
|
||||||
sqlite3.tempDirectory = cachebase;
|
sqlite3.tempDirectory = cachebase;
|
||||||
|
|
||||||
Sqlite3Vec.ensureExtensionLoaded();
|
|
||||||
|
|
||||||
return NativeDatabase.createInBackground(
|
return NativeDatabase.createInBackground(
|
||||||
file,
|
file,
|
||||||
setup: (database) {
|
setup: (database) {
|
||||||
@@ -46,7 +40,6 @@ TabDatabase tabDatabase(Ref ref) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
embeddingDimensions: dimensions,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.onDispose(() async {
|
ref.onDispose(() async {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ part of 'providers.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$tabDatabaseHash() => r'db190f01d7e70e65b9f44403effa82ec503144a0';
|
String _$tabDatabaseHash() => r'422bd4789296dc271fabbd5906f2e2ab16bccaa3';
|
||||||
|
|
||||||
/// See also [tabDatabase].
|
/// See also [tabDatabase].
|
||||||
@ProviderFor(tabDatabase)
|
@ProviderFor(tabDatabase)
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:exceptions/exceptions.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/chat_metadata.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/message_types.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/providers.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/repositories/chat_message.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/chat/services/qa_memory_chain.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/domain/repositories/document.dart';
|
|
||||||
|
|
||||||
part 'chat_backend.g.dart';
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class ChatBackend extends _$ChatBackend {
|
|
||||||
late ChatMetadata? _metadata;
|
|
||||||
|
|
||||||
late ChatMessageRepository _chatRepository;
|
|
||||||
|
|
||||||
late QAMemoryChain _qaMemoryChain;
|
|
||||||
|
|
||||||
Future<void>? _embeddingsUpdate;
|
|
||||||
Future<void> prepareEmbeddings() {
|
|
||||||
if (_embeddingsUpdate != null) {
|
|
||||||
return _embeddingsUpdate!;
|
|
||||||
}
|
|
||||||
|
|
||||||
_embeddingsUpdate = ref
|
|
||||||
.read(documentRepositoryProvider.notifier)
|
|
||||||
.updateEmbeddings(
|
|
||||||
mainDocumentId: _metadata?.mainDocumentId,
|
|
||||||
contextId: _metadata?.contextId,
|
|
||||||
);
|
|
||||||
|
|
||||||
_embeddingsUpdate!.whenComplete(() => _embeddingsUpdate = null);
|
|
||||||
|
|
||||||
return _embeddingsUpdate!;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result<void>> processQAMessage(String input) async {
|
|
||||||
//TODO: fix result mess
|
|
||||||
|
|
||||||
final humanMessageResult = await _chatRepository.insertTextMessage(
|
|
||||||
author: MessageAuthor.human,
|
|
||||||
content: input,
|
|
||||||
);
|
|
||||||
|
|
||||||
await prepareEmbeddings();
|
|
||||||
|
|
||||||
return humanMessageResult.flatMapAsync((humanMessage) async {
|
|
||||||
await _chatRepository.setTyping(author: MessageAuthor.ai, typing: true);
|
|
||||||
|
|
||||||
final result = await _qaMemoryChain.processQuestion(humanMessage.text);
|
|
||||||
await _chatRepository.setTyping(author: MessageAuthor.ai, typing: false);
|
|
||||||
|
|
||||||
await _chatRepository.insertTextMessage(
|
|
||||||
author: MessageAuthor.ai,
|
|
||||||
content: result.answer,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void build(String chatId) {
|
|
||||||
_metadata = ref.watch(
|
|
||||||
chatMetadataProvider(chatId).select((value) => value.valueOrNull),
|
|
||||||
);
|
|
||||||
|
|
||||||
_chatRepository = ref.watch(chatMessageRepositoryProvider(chatId).notifier);
|
|
||||||
|
|
||||||
_qaMemoryChain = ref.watch(
|
|
||||||
qAMemoryChainProvider(
|
|
||||||
chatId: chatId,
|
|
||||||
mainDocumentId: _metadata?.mainDocumentId,
|
|
||||||
contextId: _metadata?.contextId,
|
|
||||||
).notifier,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'chat_backend.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$chatBackendHash() => r'befdad9878bf0a1a8a196af5e3006978026eb8ba';
|
|
||||||
|
|
||||||
/// 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 _$ChatBackend extends BuildlessAutoDisposeNotifier<void> {
|
|
||||||
late final String chatId;
|
|
||||||
|
|
||||||
void build(String chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
@ProviderFor(ChatBackend)
|
|
||||||
const chatBackendProvider = ChatBackendFamily();
|
|
||||||
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
class ChatBackendFamily extends Family<void> {
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
const ChatBackendFamily();
|
|
||||||
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
ChatBackendProvider call(String chatId) {
|
|
||||||
return ChatBackendProvider(chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
ChatBackendProvider getProviderOverride(
|
|
||||||
covariant ChatBackendProvider 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'chatBackendProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
class ChatBackendProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<ChatBackend, void> {
|
|
||||||
/// See also [ChatBackend].
|
|
||||||
ChatBackendProvider(String chatId)
|
|
||||||
: this._internal(
|
|
||||||
() => ChatBackend()..chatId = chatId,
|
|
||||||
from: chatBackendProvider,
|
|
||||||
name: r'chatBackendProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$chatBackendHash,
|
|
||||||
dependencies: ChatBackendFamily._dependencies,
|
|
||||||
allTransitiveDependencies: ChatBackendFamily._allTransitiveDependencies,
|
|
||||||
chatId: chatId,
|
|
||||||
);
|
|
||||||
|
|
||||||
ChatBackendProvider._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 ChatBackend notifier) {
|
|
||||||
return notifier.build(chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(ChatBackend Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: ChatBackendProvider._internal(
|
|
||||||
() => create()..chatId = chatId,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
chatId: chatId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<ChatBackend, void> createElement() {
|
|
||||||
return _ChatBackendProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is ChatBackendProvider && 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 ChatBackendRef on AutoDisposeNotifierProviderRef<void> {
|
|
||||||
/// The parameter `chatId` of this provider.
|
|
||||||
String get chatId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ChatBackendProviderElement
|
|
||||||
extends AutoDisposeNotifierProviderElement<ChatBackend, void>
|
|
||||||
with ChatBackendRef {
|
|
||||||
_ChatBackendProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get chatId => (origin as ChatBackendProvider).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
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class ChatTextMessage extends StatelessWidget {
|
|
||||||
final TextMessage message;
|
|
||||||
final int index;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
final BorderRadiusGeometry? borderRadius;
|
|
||||||
final double? onlyEmojiFontSize;
|
|
||||||
|
|
||||||
const ChatTextMessage({
|
|
||||||
super.key,
|
|
||||||
required this.message,
|
|
||||||
required this.index,
|
|
||||||
this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
||||||
this.borderRadius = const BorderRadius.all(Radius.circular(12)),
|
|
||||||
this.onlyEmojiFontSize = 48,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final textMessageTheme = context.select(
|
|
||||||
(ChatTheme theme) => theme.textMessageTheme,
|
|
||||||
);
|
|
||||||
final isSentByMe = context.watch<String>() == message.authorId;
|
|
||||||
final paragraphStyle =
|
|
||||||
isSentByMe
|
|
||||||
? textMessageTheme.sentTextStyle
|
|
||||||
: textMessageTheme.receivedTextStyle;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: padding,
|
|
||||||
decoration:
|
|
||||||
message.isOnlyEmoji == true
|
|
||||||
? null
|
|
||||||
: BoxDecoration(
|
|
||||||
color:
|
|
||||||
isSentByMe
|
|
||||||
? textMessageTheme.sentBackgroundColor
|
|
||||||
: textMessageTheme.receivedBackgroundColor,
|
|
||||||
borderRadius: borderRadius,
|
|
||||||
),
|
|
||||||
child: MarkdownBody(
|
|
||||||
data: message.text,
|
|
||||||
selectable: true,
|
|
||||||
styleSheet: MarkdownStyleSheet(
|
|
||||||
p:
|
|
||||||
message.isOnlyEmoji == true
|
|
||||||
? paragraphStyle?.copyWith(fontSize: onlyEmojiFontSize)
|
|
||||||
: paragraphStyle,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-186
@@ -1,186 +0,0 @@
|
|||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:weblibre/presentation/widgets/speech_to_text_button.dart';
|
|
||||||
|
|
||||||
typedef OnMessageTapCallback = void Function(Message message);
|
|
||||||
typedef OnMessageSendCallback = void Function(String text);
|
|
||||||
typedef OnAttachmentTapCallback = VoidCallback;
|
|
||||||
|
|
||||||
class QaChatInput extends StatefulWidget {
|
|
||||||
final double? left;
|
|
||||||
final double? right;
|
|
||||||
final double? top;
|
|
||||||
final double? bottom;
|
|
||||||
final double? sigmaX;
|
|
||||||
final double? sigmaY;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
final Widget? attachmentIcon;
|
|
||||||
final Widget? sendIcon;
|
|
||||||
final double? gap;
|
|
||||||
final InputBorder? inputBorder;
|
|
||||||
final bool? filled;
|
|
||||||
final Widget? topWidget;
|
|
||||||
final bool? handleSafeArea;
|
|
||||||
|
|
||||||
const QaChatInput({
|
|
||||||
super.key,
|
|
||||||
this.left = 0,
|
|
||||||
this.right = 0,
|
|
||||||
this.top,
|
|
||||||
this.bottom = 0,
|
|
||||||
this.sigmaX = 20,
|
|
||||||
this.sigmaY = 20,
|
|
||||||
this.padding = const EdgeInsets.all(8.0),
|
|
||||||
this.attachmentIcon = const Icon(Icons.attachment),
|
|
||||||
this.sendIcon = const Icon(Icons.send),
|
|
||||||
this.gap = 8,
|
|
||||||
this.inputBorder = const OutlineInputBorder(
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
|
||||||
),
|
|
||||||
this.filled = true,
|
|
||||||
this.topWidget,
|
|
||||||
this.handleSafeArea = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<QaChatInput> createState() => _QaChatInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QaChatInputState extends State<QaChatInput> {
|
|
||||||
final GlobalKey _inputKey = GlobalKey();
|
|
||||||
final TextEditingController _textController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _updateInputHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant QaChatInput oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _updateInputHeight());
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_textController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final bottomSafeArea =
|
|
||||||
widget.handleSafeArea == true
|
|
||||||
? MediaQuery.of(context).padding.bottom
|
|
||||||
: 0.0;
|
|
||||||
final inputTheme = context.select((ChatTheme theme) => theme.inputTheme);
|
|
||||||
final onAttachmentTap = context.read<OnAttachmentTapCallback?>();
|
|
||||||
|
|
||||||
return Positioned(
|
|
||||||
left: widget.left,
|
|
||||||
right: widget.right,
|
|
||||||
top: widget.top,
|
|
||||||
bottom: widget.bottom,
|
|
||||||
child: ClipRect(
|
|
||||||
child: BackdropFilter(
|
|
||||||
filter: ImageFilter.blur(
|
|
||||||
// TODO: remove backdrop filter if both are 0
|
|
||||||
sigmaX: widget.sigmaX ?? 0,
|
|
||||||
sigmaY: widget.sigmaY ?? 0,
|
|
||||||
),
|
|
||||||
child: Container(
|
|
||||||
key: _inputKey,
|
|
||||||
color: inputTheme.backgroundColor,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
if (widget.topWidget != null) widget.topWidget!,
|
|
||||||
Padding(
|
|
||||||
padding:
|
|
||||||
widget.handleSafeArea == true
|
|
||||||
? (widget.padding?.add(
|
|
||||||
EdgeInsets.only(bottom: bottomSafeArea),
|
|
||||||
) ??
|
|
||||||
EdgeInsets.only(bottom: bottomSafeArea))
|
|
||||||
: (widget.padding ?? EdgeInsets.zero),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
if (widget.attachmentIcon != null)
|
|
||||||
IconButton(
|
|
||||||
icon: widget.attachmentIcon!,
|
|
||||||
color: inputTheme.hintStyle?.color,
|
|
||||||
onPressed: onAttachmentTap,
|
|
||||||
)
|
|
||||||
else
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
SizedBox(width: widget.gap),
|
|
||||||
Expanded(
|
|
||||||
child: TextField(
|
|
||||||
controller: _textController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'Type a message',
|
|
||||||
hintStyle: inputTheme.hintStyle,
|
|
||||||
border: widget.inputBorder,
|
|
||||||
filled: widget.filled,
|
|
||||||
fillColor: inputTheme.textFieldColor,
|
|
||||||
hoverColor: Colors.transparent,
|
|
||||||
suffixIcon: SpeechToTextButton(
|
|
||||||
onTextReceived: (data) {
|
|
||||||
_textController.text = data.toString();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
style: inputTheme.textStyle,
|
|
||||||
onSubmitted: _handleSubmitted,
|
|
||||||
textInputAction: TextInputAction.send,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: widget.gap),
|
|
||||||
if (widget.sendIcon != null)
|
|
||||||
IconButton(
|
|
||||||
icon: widget.sendIcon!,
|
|
||||||
color: inputTheme.hintStyle?.color,
|
|
||||||
onPressed:
|
|
||||||
() => _handleSubmitted(_textController.text),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateInputHeight() {
|
|
||||||
if (!mounted) return;
|
|
||||||
|
|
||||||
final renderBox =
|
|
||||||
_inputKey.currentContext?.findRenderObject() as RenderBox?;
|
|
||||||
if (renderBox != null) {
|
|
||||||
final height = renderBox.size.height;
|
|
||||||
final bottomSafeArea = MediaQuery.of(context).padding.bottom;
|
|
||||||
|
|
||||||
context.read<ChatInputHeightNotifier>().updateHeight(
|
|
||||||
// only set real height of the input, ignoring safe area
|
|
||||||
widget.handleSafeArea == true ? height - bottomSafeArea : height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleSubmitted(String text) {
|
|
||||||
if (text.isNotEmpty) {
|
|
||||||
context.read<OnMessageSendCallback?>()?.call(text);
|
|
||||||
_textController.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_chat_core/flutter_chat_core.dart';
|
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/data/models/message_types.dart';
|
|
||||||
import 'package:weblibre/features/chat/features/chat_store/domain/providers.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/chat/domain/chat_backend.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/chat/presentation/widgets/chat_text_message.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/chat/presentation/widgets/qa_chat_input.dart';
|
|
||||||
import 'package:weblibre/presentation/hooks/on_initialization.dart';
|
|
||||||
|
|
||||||
class TabQaChat extends HookConsumerWidget {
|
|
||||||
final String chatId;
|
|
||||||
final ScrollController? scrollController;
|
|
||||||
|
|
||||||
const TabQaChat({required this.chatId, this.scrollController, super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final chatBackend = ref.watch(chatBackendProvider(chatId).notifier);
|
|
||||||
final chatController = ref.watch(chatControllerProvider(chatId));
|
|
||||||
|
|
||||||
// final crossCache = useMemoized(() => CrossCache());
|
|
||||||
final chatScrollController = scrollController ?? useScrollController();
|
|
||||||
|
|
||||||
useOnInitialization(() async {
|
|
||||||
await chatBackend.prepareEmbeddings();
|
|
||||||
});
|
|
||||||
|
|
||||||
return Chat(
|
|
||||||
darkTheme: ChatTheme.dark(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
||||||
inputTheme: InputTheme(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
theme: ChatTheme.light(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
builders: Builders(
|
|
||||||
textMessageBuilder:
|
|
||||||
(context, message, index) =>
|
|
||||||
ChatTextMessage(message: message, index: index),
|
|
||||||
customMessageBuilder:
|
|
||||||
(context, message, index) => Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
|
||||||
decoration: const BoxDecoration(
|
|
||||||
color: Color(0xFFF0F0F0),
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
|
||||||
),
|
|
||||||
child: const IsTypingIndicator(),
|
|
||||||
),
|
|
||||||
inputBuilder: (context) => const QaChatInput(attachmentIcon: null),
|
|
||||||
),
|
|
||||||
chatController: chatController,
|
|
||||||
// crossCache: crossCache,
|
|
||||||
scrollController: chatScrollController,
|
|
||||||
onMessageSend: (text) async {
|
|
||||||
await chatBackend.processQAMessage(text);
|
|
||||||
},
|
|
||||||
currentUserId: MessageAuthor.human.user.id,
|
|
||||||
resolveUser:
|
|
||||||
(id) => Future.value(
|
|
||||||
MessageAuthor.values
|
|
||||||
.firstWhereOrNull((user) => user.user.id == id)
|
|
||||||
?.user,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:langchain_openai/langchain_openai.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/core/providers/models.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/domain/sqlite_vector_store.dart';
|
|
||||||
|
|
||||||
part 'qa_memory_chain.g.dart';
|
|
||||||
|
|
||||||
typedef QAResult = ({String answer, List<Document> docs});
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class QAMemoryChain extends _$QAMemoryChain {
|
|
||||||
late RetrievalQAChain _retrievalQA;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void build({
|
|
||||||
required String chatId,
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
final chatModel = ref.watch(chatModelProvider);
|
|
||||||
final embeddingsModel = ref.watch(embeddingModelProvider);
|
|
||||||
final db = ref.watch(tabDatabaseProvider);
|
|
||||||
|
|
||||||
final retriever = SqliteVectorStore(
|
|
||||||
db.vectorDao,
|
|
||||||
embeddings: embeddingsModel,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
).asRetriever(
|
|
||||||
defaultOptions: const VectorStoreRetrieverOptions(
|
|
||||||
searchType: VectorStoreSimilaritySearch(k: 6),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final qaChain = OpenAIQAWithSourcesChain(llm: chatModel);
|
|
||||||
final docPrompt = PromptTemplate.fromTemplate(
|
|
||||||
'Content: {page_content}\nSource: {source}',
|
|
||||||
);
|
|
||||||
|
|
||||||
final finalQAChain = StuffDocumentsChain(
|
|
||||||
llmChain: qaChain,
|
|
||||||
documentPrompt: docPrompt,
|
|
||||||
);
|
|
||||||
|
|
||||||
_retrievalQA = RetrievalQAChain(
|
|
||||||
retriever: retriever,
|
|
||||||
combineDocumentsChain: finalQAChain,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<QAResult> processQuestion(String input) async {
|
|
||||||
final result = await _retrievalQA(input);
|
|
||||||
final qaResult = result['result'] as QAWithSources;
|
|
||||||
|
|
||||||
return (answer: qaResult.answer, docs: <Document>[]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-219
@@ -1,219 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'qa_memory_chain.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$qAMemoryChainHash() => r'f05ff182416cd8ac8c35f046081652f8964684a8';
|
|
||||||
|
|
||||||
/// 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 _$QAMemoryChain extends BuildlessAutoDisposeNotifier<void> {
|
|
||||||
late final String chatId;
|
|
||||||
late final String? mainDocumentId;
|
|
||||||
late final String? contextId;
|
|
||||||
|
|
||||||
void build({
|
|
||||||
required String chatId,
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
@ProviderFor(QAMemoryChain)
|
|
||||||
const qAMemoryChainProvider = QAMemoryChainFamily();
|
|
||||||
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
class QAMemoryChainFamily extends Family<void> {
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
const QAMemoryChainFamily();
|
|
||||||
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
QAMemoryChainProvider call({
|
|
||||||
required String chatId,
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
return QAMemoryChainProvider(
|
|
||||||
chatId: chatId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
QAMemoryChainProvider getProviderOverride(
|
|
||||||
covariant QAMemoryChainProvider provider,
|
|
||||||
) {
|
|
||||||
return call(
|
|
||||||
chatId: provider.chatId,
|
|
||||||
mainDocumentId: provider.mainDocumentId,
|
|
||||||
contextId: provider.contextId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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'qAMemoryChainProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
class QAMemoryChainProvider
|
|
||||||
extends AutoDisposeNotifierProviderImpl<QAMemoryChain, void> {
|
|
||||||
/// See also [QAMemoryChain].
|
|
||||||
QAMemoryChainProvider({
|
|
||||||
required String chatId,
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) : this._internal(
|
|
||||||
() =>
|
|
||||||
QAMemoryChain()
|
|
||||||
..chatId = chatId
|
|
||||||
..mainDocumentId = mainDocumentId
|
|
||||||
..contextId = contextId,
|
|
||||||
from: qAMemoryChainProvider,
|
|
||||||
name: r'qAMemoryChainProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$qAMemoryChainHash,
|
|
||||||
dependencies: QAMemoryChainFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
QAMemoryChainFamily._allTransitiveDependencies,
|
|
||||||
chatId: chatId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
);
|
|
||||||
|
|
||||||
QAMemoryChainProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.chatId,
|
|
||||||
required this.mainDocumentId,
|
|
||||||
required this.contextId,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final String chatId;
|
|
||||||
final String? mainDocumentId;
|
|
||||||
final String? contextId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void runNotifierBuild(covariant QAMemoryChain notifier) {
|
|
||||||
return notifier.build(
|
|
||||||
chatId: chatId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(QAMemoryChain Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: QAMemoryChainProvider._internal(
|
|
||||||
() =>
|
|
||||||
create()
|
|
||||||
..chatId = chatId
|
|
||||||
..mainDocumentId = mainDocumentId
|
|
||||||
..contextId = contextId,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
chatId: chatId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeNotifierProviderElement<QAMemoryChain, void> createElement() {
|
|
||||||
return _QAMemoryChainProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is QAMemoryChainProvider &&
|
|
||||||
other.chatId == chatId &&
|
|
||||||
other.mainDocumentId == mainDocumentId &&
|
|
||||||
other.contextId == contextId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, chatId.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, mainDocumentId.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, contextId.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
mixin QAMemoryChainRef on AutoDisposeNotifierProviderRef<void> {
|
|
||||||
/// The parameter `chatId` of this provider.
|
|
||||||
String get chatId;
|
|
||||||
|
|
||||||
/// The parameter `mainDocumentId` of this provider.
|
|
||||||
String? get mainDocumentId;
|
|
||||||
|
|
||||||
/// The parameter `contextId` of this provider.
|
|
||||||
String? get contextId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _QAMemoryChainProviderElement
|
|
||||||
extends AutoDisposeNotifierProviderElement<QAMemoryChain, void>
|
|
||||||
with QAMemoryChainRef {
|
|
||||||
_QAMemoryChainProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get chatId => (origin as QAMemoryChainProvider).chatId;
|
|
||||||
@override
|
|
||||||
String? get mainDocumentId =>
|
|
||||||
(origin as QAMemoryChainProvider).mainDocumentId;
|
|
||||||
@override
|
|
||||||
String? get contextId => (origin as QAMemoryChainProvider).contextId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
-173
@@ -1,173 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:langchain/langchain.dart' as langchain;
|
|
||||||
import 'package:weblibre/core/uuid.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/utils/hash.dart';
|
|
||||||
import 'package:weblibre/utils/langchain_utils.dart';
|
|
||||||
|
|
||||||
part 'vector.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor()
|
|
||||||
class VectorDao extends DatabaseAccessor<TabDatabase> with _$VectorDaoMixin {
|
|
||||||
VectorDao(super.attachedDatabase);
|
|
||||||
|
|
||||||
Future<int> deleteDocuments(List<String> ids) {
|
|
||||||
return db.document.deleteWhere((doc) => doc.documentId.isIn(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> deleteDocumentsByMainDocumentId(String mainDocumentId) {
|
|
||||||
return db.document.deleteWhere(
|
|
||||||
(doc) => doc.mainDocumentId.equals(mainDocumentId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
SingleOrNullSelectable<DocumentData> getDocumentById(String documentId) {
|
|
||||||
return db.document.select()
|
|
||||||
..where((row) => row.documentId.equals(documentId));
|
|
||||||
}
|
|
||||||
|
|
||||||
Selectable<DocumentData> getDocuments({
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
final statement = db.document.select();
|
|
||||||
|
|
||||||
if (mainDocumentId != null) {
|
|
||||||
statement.where((row) => row.mainDocumentId.equals(mainDocumentId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextId != null) {
|
|
||||||
statement.where((row) => row.contextId.equals(contextId));
|
|
||||||
}
|
|
||||||
|
|
||||||
return statement;
|
|
||||||
}
|
|
||||||
|
|
||||||
Selectable<DocumentData> getDocumentsWithMissingEmbeddings({
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
return db.missingDocumentEmbeddings(
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> insertDocuments(
|
|
||||||
List<langchain.Document> documents, {
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
return db.document.insertAll(
|
|
||||||
documents.map((document) {
|
|
||||||
final documentId = document.id ?? uuid.v4();
|
|
||||||
final contentHash = sha2(document.pageContent);
|
|
||||||
|
|
||||||
return DocumentCompanion.insert(
|
|
||||||
documentId: documentId,
|
|
||||||
mainDocumentId: Value.absentIfNull(mainDocumentId),
|
|
||||||
contextId: Value.absentIfNull(contextId),
|
|
||||||
content: document.pageContent,
|
|
||||||
metadata:
|
|
||||||
(document.metadata.isNotEmpty)
|
|
||||||
? Value(jsonEncode(document.metadata))
|
|
||||||
: const Value.absent(),
|
|
||||||
contentHash: contentHash,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> insertEmbeddings(List<(DocumentData, List<double>)> documents) {
|
|
||||||
return db.documentVec.insertAll(
|
|
||||||
documents.map((doc) {
|
|
||||||
final (document, vector) = doc;
|
|
||||||
|
|
||||||
return DocumentVecCompanion.insert(
|
|
||||||
id: document.documentId,
|
|
||||||
embedding: serializeVector(vector),
|
|
||||||
mainDocumentId: Value.absentIfNull(document.mainDocumentId),
|
|
||||||
contextId: Value.absentIfNull(document.contextId),
|
|
||||||
contentHash: document.contentHash,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> insertDocumentsWithEmbedings(
|
|
||||||
List<(langchain.Document, List<double>)> documents, {
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) async {
|
|
||||||
final insertedIds = <String>[];
|
|
||||||
|
|
||||||
await db.batch((b) async {
|
|
||||||
final insertableDocuments = <Insertable<DocumentData>>[];
|
|
||||||
final insertableEmbeddings = <Insertable<DocumentVecData>>[];
|
|
||||||
|
|
||||||
for (final (document, vector) in documents) {
|
|
||||||
final documentId = document.id ?? uuid.v4();
|
|
||||||
final contentHash = sha2(document.pageContent);
|
|
||||||
|
|
||||||
insertedIds.add(documentId);
|
|
||||||
|
|
||||||
insertableDocuments.add(
|
|
||||||
DocumentCompanion.insert(
|
|
||||||
documentId: documentId,
|
|
||||||
mainDocumentId: Value.absentIfNull(mainDocumentId),
|
|
||||||
contextId: Value.absentIfNull(contextId),
|
|
||||||
content: document.pageContent,
|
|
||||||
metadata:
|
|
||||||
(document.metadata.isNotEmpty)
|
|
||||||
? Value(jsonEncode(document.metadata))
|
|
||||||
: const Value.absent(),
|
|
||||||
contentHash: contentHash,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
insertableEmbeddings.add(
|
|
||||||
DocumentVecCompanion.insert(
|
|
||||||
id: documentId,
|
|
||||||
embedding: serializeVector(vector),
|
|
||||||
mainDocumentId: Value.absentIfNull(mainDocumentId),
|
|
||||||
contextId: Value.absentIfNull(contextId),
|
|
||||||
contentHash: contentHash,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.document.insertAll(
|
|
||||||
insertableDocuments,
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
|
|
||||||
await db.documentVec.insertAll(insertableEmbeddings);
|
|
||||||
});
|
|
||||||
|
|
||||||
return insertedIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
Selectable<VectorResult> vectorSearch({
|
|
||||||
required langchain.VectorStoreSimilaritySearch config,
|
|
||||||
required List<double> searchVectors,
|
|
||||||
String? mainDocumentId,
|
|
||||||
String? contextId,
|
|
||||||
}) {
|
|
||||||
assert(config.filter?.isNotEmpty ?? true, 'Filters are unsupported');
|
|
||||||
assert(
|
|
||||||
!(mainDocumentId != null && contextId != null),
|
|
||||||
'Either filter by document or context, not both',
|
|
||||||
);
|
|
||||||
|
|
||||||
return db.queryVectors(
|
|
||||||
searchVectors: serializeVector(searchVectors),
|
|
||||||
k: config.k,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'vector.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$VectorDaoMixin on DatabaseAccessor<TabDatabase> {}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
class VectorDatabaseMigrator {
|
|
||||||
final int dimensions;
|
|
||||||
|
|
||||||
String get vectorTableDefinition => '''
|
|
||||||
CREATE VIRTUAL TABLE document_vec using vec0(
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
main_document_id TEXT,
|
|
||||||
context_id TEXT PARTITION KEY,
|
|
||||||
embedding float[$dimensions],
|
|
||||||
content_hash TEXT
|
|
||||||
);
|
|
||||||
''';
|
|
||||||
|
|
||||||
VectorDatabaseMigrator({required this.dimensions});
|
|
||||||
}
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
|
|
||||||
|
|
||||||
CREATE TABLE document(
|
|
||||||
document_id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
main_document_id TEXT,
|
|
||||||
context_id TEXT,
|
|
||||||
content TEXT NOT NULL,
|
|
||||||
content_hash TEXT NOT NULL,
|
|
||||||
metadata TEXT
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX document_main_document_id ON document (main_document_id);
|
|
||||||
CREATE INDEX document_context_id ON document (context_id);
|
|
||||||
|
|
||||||
CREATE TABLE document_vec(
|
|
||||||
-- dummy definition of virtual table
|
|
||||||
-- keep in sync with defined table definition
|
|
||||||
|
|
||||||
id TEXT NOT NULL PRIMARY KEY,
|
|
||||||
main_document_id TEXT,
|
|
||||||
context_id TEXT,
|
|
||||||
embedding BLOB NOT NULL,
|
|
||||||
content_hash TEXT NOT NULL,
|
|
||||||
|
|
||||||
distance REAL,
|
|
||||||
k INTEGER
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TRIGGER document_delete AFTER DELETE ON document BEGIN
|
|
||||||
DELETE FROM document_vec WHERE id = old.document_id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER document_update_delete AFTER UPDATE ON document BEGIN
|
|
||||||
DELETE FROM document_vec WHERE id = new.document_id AND content_hash != new.content_hash;
|
|
||||||
END;
|
|
||||||
|
|
||||||
missingDocumentEmbeddings(
|
|
||||||
:main_document_id AS TEXT OR NULL,
|
|
||||||
:context_id AS TEXT OR NULL
|
|
||||||
):
|
|
||||||
SELECT
|
|
||||||
doc.*
|
|
||||||
FROM document doc
|
|
||||||
WHERE
|
|
||||||
doc.main_document_id IS COALESCE(:main_document_id, doc.main_document_id) AND
|
|
||||||
doc.context_id IS COALESCE(:context_id, doc.context_id) AND
|
|
||||||
NOT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM document_vec vec
|
|
||||||
WHERE vec.id = doc.document_id
|
|
||||||
);
|
|
||||||
|
|
||||||
queryVectors(
|
|
||||||
:searchVectors AS BLOB,
|
|
||||||
:main_document_id AS TEXT OR NULL,
|
|
||||||
:context_id AS TEXT OR NULL
|
|
||||||
) WITH VectorResult:
|
|
||||||
SELECT
|
|
||||||
vec.id,
|
|
||||||
doc.main_document_id,
|
|
||||||
doc.context_id,
|
|
||||||
doc.content,
|
|
||||||
doc.metadata,
|
|
||||||
vec.distance
|
|
||||||
FROM document_vec vec
|
|
||||||
INNER JOIN document doc ON doc.document_id = vec.id
|
|
||||||
WHERE
|
|
||||||
vec.embedding MATCH :searchVectors AND
|
|
||||||
vec.k = :k AND
|
|
||||||
vec.main_document_id IS COALESCE(:main_document_id, vec.main_document_id) AND
|
|
||||||
vec.context_id IS COALESCE(:context_id, vec.context_id)
|
|
||||||
ORDER BY vec.distance;
|
|
||||||
-40
@@ -1,40 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:fast_equatable/fast_equatable.dart';
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
|
|
||||||
class VectorResult with FastEquatable {
|
|
||||||
final String id;
|
|
||||||
final String? mainDocumentId;
|
|
||||||
final String? contextId;
|
|
||||||
final String content;
|
|
||||||
final Map<String, dynamic> metadata;
|
|
||||||
final double distance;
|
|
||||||
|
|
||||||
VectorResult({
|
|
||||||
required this.id,
|
|
||||||
required this.mainDocumentId,
|
|
||||||
required this.contextId,
|
|
||||||
required this.content,
|
|
||||||
required String? metadata,
|
|
||||||
required double? distance,
|
|
||||||
}) : metadata =
|
|
||||||
(metadata != null)
|
|
||||||
? jsonDecode(metadata) as Map<String, dynamic>
|
|
||||||
: const {},
|
|
||||||
distance = distance!;
|
|
||||||
|
|
||||||
Document toDocument() {
|
|
||||||
return Document(id: id, pageContent: content, metadata: metadata);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<Object?> get hashParameters => [
|
|
||||||
id,
|
|
||||||
mainDocumentId,
|
|
||||||
contextId,
|
|
||||||
content,
|
|
||||||
metadata,
|
|
||||||
distance,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
-115
@@ -1,115 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:exceptions/exceptions.dart';
|
|
||||||
import 'package:langchain/langchain.dart' as langchain;
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:langchain_openai/langchain_openai.dart';
|
|
||||||
import 'package:nullability/nullability.dart';
|
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
||||||
import 'package:weblibre/core/providers/models.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/domain/providers.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/data/providers.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/utils/markdown_document_splitter.dart';
|
|
||||||
|
|
||||||
part 'document.g.dart';
|
|
||||||
|
|
||||||
@Riverpod()
|
|
||||||
class DocumentRepository extends _$DocumentRepository {
|
|
||||||
late VectorDao _vectorDao;
|
|
||||||
late OpenAIEmbeddings _embeddings;
|
|
||||||
|
|
||||||
Future<void> _insertMarkdownDocumentsSplitted(
|
|
||||||
List<langchain.Document> originalDocuments, {
|
|
||||||
List<(String, String)> headersToSplitOn = const [
|
|
||||||
('#', 'h1'),
|
|
||||||
('##', 'h2'),
|
|
||||||
('###', 'h3'),
|
|
||||||
('####', 'h4'),
|
|
||||||
('#####', 'h5'),
|
|
||||||
('######', 'h6'),
|
|
||||||
],
|
|
||||||
int chunkSize = 748,
|
|
||||||
int chunkOverlap = 150,
|
|
||||||
}) {
|
|
||||||
return _vectorDao.transaction(() async {
|
|
||||||
for (final doc in originalDocuments) {
|
|
||||||
final splitted = splitMarkdownDocument(
|
|
||||||
doc,
|
|
||||||
headersToSplitOn: headersToSplitOn,
|
|
||||||
chunkSize: chunkSize,
|
|
||||||
chunkOverlap: chunkOverlap,
|
|
||||||
);
|
|
||||||
|
|
||||||
final splittedWithSource = splitted.mapNotNull(
|
|
||||||
(splitted) => (
|
|
||||||
mainDocumentId: splitted.mainDocumentId,
|
|
||||||
parts:
|
|
||||||
splitted.parts
|
|
||||||
.map(
|
|
||||||
(part) => part.copyWith(
|
|
||||||
metadata: {...part.metadata, 'source': part.id},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await _vectorDao.insertDocuments(
|
|
||||||
splittedWithSource?.parts ?? [doc],
|
|
||||||
mainDocumentId: splittedWithSource?.mainDocumentId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Result> updateEmbeddings({String? mainDocumentId, String? contextId}) {
|
|
||||||
return Result.fromAsync(() async {
|
|
||||||
final missing =
|
|
||||||
await _vectorDao
|
|
||||||
.getDocumentsWithMissingEmbeddings(
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
contextId: contextId,
|
|
||||||
)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
final documentEmbeddings = await _embeddings.embedDocuments(
|
|
||||||
missing
|
|
||||||
.map((doc) => langchain.Document(pageContent: doc.content))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
return _vectorDao.insertEmbeddings(
|
|
||||||
missing.mapIndexed((i, doc) => (doc, documentEmbeddings[i])).toList(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void build() {
|
|
||||||
_vectorDao = ref.watch(tabDatabaseProvider).vectorDao;
|
|
||||||
_embeddings = ref.watch(embeddingModelProvider);
|
|
||||||
|
|
||||||
final tabContentService = ref.watch(tabContentServiceProvider);
|
|
||||||
|
|
||||||
final tabContentSub = tabContentService.tabContentStream.listen((
|
|
||||||
content,
|
|
||||||
) async {
|
|
||||||
final bestContent =
|
|
||||||
content.isProbablyReaderable
|
|
||||||
? content.extractedContentMarkdown
|
|
||||||
: content.fullContentMarkdown;
|
|
||||||
|
|
||||||
if (bestContent != null) {
|
|
||||||
await _insertMarkdownDocumentsSplitted([
|
|
||||||
Document(id: content.tabId, pageContent: bestContent),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ref.onDispose(() async {
|
|
||||||
await tabContentSub.cancel();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'document.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// RiverpodGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
String _$documentRepositoryHash() =>
|
|
||||||
r'fd78b5eaf96951724e905b068144dcf2a254dcbb';
|
|
||||||
|
|
||||||
/// See also [DocumentRepository].
|
|
||||||
@ProviderFor(DocumentRepository)
|
|
||||||
final documentRepositoryProvider =
|
|
||||||
AutoDisposeNotifierProvider<DocumentRepository, void>.internal(
|
|
||||||
DocumentRepository.new,
|
|
||||||
name: r'documentRepositoryProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$documentRepositoryHash,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
typedef _$DocumentRepository = AutoDisposeNotifier<void>;
|
|
||||||
// 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
|
|
||||||
-61
@@ -1,61 +0,0 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/database/daos/vector.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/data/models/vector_result.dart';
|
|
||||||
|
|
||||||
class SqliteVectorStore extends VectorStore {
|
|
||||||
final VectorDao _dao;
|
|
||||||
|
|
||||||
final String? mainDocumentId;
|
|
||||||
final String? contextId;
|
|
||||||
|
|
||||||
SqliteVectorStore(
|
|
||||||
this._dao, {
|
|
||||||
required super.embeddings,
|
|
||||||
this.mainDocumentId,
|
|
||||||
this.contextId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<String>> addVectors({
|
|
||||||
required List<Document> documents,
|
|
||||||
required List<List<double>> vectors,
|
|
||||||
}) {
|
|
||||||
return _dao.insertDocumentsWithEmbedings(
|
|
||||||
documents.mapIndexed((i, doc) => (doc, vectors[i])).toList(),
|
|
||||||
contextId: contextId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> delete({required List<String> ids}) {
|
|
||||||
return _dao.deleteDocuments(ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<(Document, double)>> similaritySearchByVectorWithScores({
|
|
||||||
required List<double> embedding,
|
|
||||||
VectorStoreSimilaritySearch config = const VectorStoreSimilaritySearch(),
|
|
||||||
}) async {
|
|
||||||
Iterable<VectorResult> results =
|
|
||||||
await _dao
|
|
||||||
.vectorSearch(
|
|
||||||
config: config,
|
|
||||||
searchVectors: embedding,
|
|
||||||
contextId: contextId,
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
)
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (config.scoreThreshold != null) {
|
|
||||||
results = results.where(
|
|
||||||
(result) => result.distance >= config.scoreThreshold!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
.map((result) => (result.toDocument(), result.distance))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:uuid/data.dart';
|
|
||||||
import 'package:uuid/parsing.dart';
|
|
||||||
import 'package:uuid/rng.dart';
|
|
||||||
import 'package:weblibre/core/uuid.dart';
|
|
||||||
|
|
||||||
final _rng = CryptoRNG();
|
|
||||||
|
|
||||||
class DocumentUuid {
|
|
||||||
final Uint8List _baseBytes;
|
|
||||||
|
|
||||||
DocumentUuid([Uint8List? baseBytes])
|
|
||||||
: _baseBytes = baseBytes ?? _rng.generate();
|
|
||||||
|
|
||||||
factory DocumentUuid.fromUuid(String uuid) {
|
|
||||||
return DocumentUuid(UuidParsing.parseAsByteList(uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
String getDocumentPartUuid(int sequence) {
|
|
||||||
final bytes = Uint8List.fromList(_baseBytes);
|
|
||||||
bytes.buffer.asByteData().setInt16(14, sequence);
|
|
||||||
|
|
||||||
return uuid.v8g(config: V8GenericOptions(bytes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
String sha2(String input) {
|
|
||||||
return sha256.convert(utf8.encode(input)).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> sha2Isolated(String input) async {
|
|
||||||
if (input.length < 1048576) {
|
|
||||||
// Less than 1MB
|
|
||||||
return sha2(input);
|
|
||||||
} else {
|
|
||||||
return await compute(sha2, input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-116
@@ -1,116 +0,0 @@
|
|||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
import 'package:weblibre/core/uuid.dart';
|
|
||||||
import 'package:weblibre/features/geckoview/features/tabs/features/vector_store/utils/document_uuid.dart';
|
|
||||||
|
|
||||||
typedef DocumentParts = ({String mainDocumentId, List<Document> parts});
|
|
||||||
|
|
||||||
List<Document> _mergeShortDocuments(
|
|
||||||
List<Document> documents,
|
|
||||||
int maxLength, {
|
|
||||||
// ignore: unused_element_parameter
|
|
||||||
String separator = '\n',
|
|
||||||
required Document Function(Document a, Document b) doMerge,
|
|
||||||
int Function(String) lengthFunction = TextSplitter.defaultLengthFunction,
|
|
||||||
}) {
|
|
||||||
final result = <Document>[];
|
|
||||||
|
|
||||||
Document? current;
|
|
||||||
for (final doc in documents) {
|
|
||||||
if (current == null) {
|
|
||||||
current = doc;
|
|
||||||
} else if (lengthFunction(current.pageContent) +
|
|
||||||
lengthFunction(doc.pageContent) <=
|
|
||||||
maxLength) {
|
|
||||||
current = doMerge(current, doc);
|
|
||||||
} else {
|
|
||||||
result.add(current);
|
|
||||||
current = doc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (current != null) {
|
|
||||||
result.add(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _headerValueList(dynamic value) {
|
|
||||||
return switch (value) {
|
|
||||||
String _ => [value],
|
|
||||||
List<String> _ => value,
|
|
||||||
_ => throw Exception('Unsupported type'),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
DocumentParts? splitMarkdownDocument(
|
|
||||||
Document originalDoc, {
|
|
||||||
required List<(String, String)> headersToSplitOn,
|
|
||||||
required int chunkSize,
|
|
||||||
required int chunkOverlap,
|
|
||||||
}) {
|
|
||||||
final markdownHeaderSplitter = MarkdownHeaderTextSplitter(
|
|
||||||
stripHeaders: false,
|
|
||||||
headersToSplitOn: headersToSplitOn,
|
|
||||||
);
|
|
||||||
|
|
||||||
final markdownTextSplitter = MarkdownTextSplitter(
|
|
||||||
chunkSize: chunkSize,
|
|
||||||
chunkOverlap: chunkOverlap,
|
|
||||||
);
|
|
||||||
|
|
||||||
final headerChunks = markdownHeaderSplitter.splitText(
|
|
||||||
originalDoc.pageContent,
|
|
||||||
);
|
|
||||||
final docChunks = markdownTextSplitter.splitDocuments(headerChunks);
|
|
||||||
|
|
||||||
if (docChunks.length == 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final headerKeys = headersToSplitOn.map((header) => header.$2).toSet();
|
|
||||||
final mergedDocChunks = _mergeShortDocuments(
|
|
||||||
docChunks,
|
|
||||||
chunkSize,
|
|
||||||
doMerge: (a, b) {
|
|
||||||
//Disallow merging into headers of the same
|
|
||||||
final intersectingHeaders = a.metadata.keys
|
|
||||||
.toSet()
|
|
||||||
.intersection(b.metadata.keys.toSet())
|
|
||||||
.intersection(headerKeys);
|
|
||||||
|
|
||||||
return Document(
|
|
||||||
id: a.id ?? b.id,
|
|
||||||
pageContent: '${a.pageContent}\n${b.pageContent}',
|
|
||||||
metadata: {
|
|
||||||
...a.metadata,
|
|
||||||
...b.metadata,
|
|
||||||
for (final header in intersectingHeaders)
|
|
||||||
header:
|
|
||||||
{
|
|
||||||
..._headerValueList(a.metadata[header]),
|
|
||||||
..._headerValueList(b.metadata[header]),
|
|
||||||
}.toList(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
final mainDocumentId = originalDoc.id ?? uuid.v4();
|
|
||||||
final documentIdFactory = DocumentUuid.fromUuid(mainDocumentId);
|
|
||||||
|
|
||||||
return (
|
|
||||||
mainDocumentId: mainDocumentId,
|
|
||||||
parts:
|
|
||||||
mergedDocChunks
|
|
||||||
.mapIndexed(
|
|
||||||
(i, doc) => Document(
|
|
||||||
id: documentIdFactory.getDocumentPartUuid(i),
|
|
||||||
pageContent: doc.pageContent,
|
|
||||||
metadata: mergeMaps(originalDoc.metadata, doc.metadata),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:langchain/langchain.dart';
|
|
||||||
|
|
||||||
String combineDocuments(List<Document> documents, {String separator = '\n\n'}) {
|
|
||||||
return documents.map((d) => d.pageContent).join(separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List serializeVector(List<double> vector) {
|
|
||||||
final buffer = Float32List.fromList(vector).buffer;
|
|
||||||
return Uint8List.view(buffer);
|
|
||||||
}
|
|
||||||
+1
-36
@@ -10,7 +10,6 @@ dependencies:
|
|||||||
background_fetch: ^1.3.8
|
background_fetch: ^1.3.8
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
copy_with_extension: ^6.0.1
|
copy_with_extension: ^6.0.1
|
||||||
crypto: ^3.0.6
|
|
||||||
drift: ^2.26.1
|
drift: ^2.26.1
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
exceptions: ^0.6.1
|
exceptions: ^0.6.1
|
||||||
@@ -20,10 +19,6 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_auto_size_text: ^4.1.0
|
flutter_auto_size_text: ^4.1.0
|
||||||
flutter_background_service: ^5.1.0
|
flutter_background_service: ^5.1.0
|
||||||
flutter_chat_core:
|
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
|
||||||
flutter_chat_ui:
|
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_ui
|
|
||||||
flutter_hooks: ^0.21.2
|
flutter_hooks: ^0.21.2
|
||||||
flutter_markdown: ^0.7.7+1
|
flutter_markdown: ^0.7.7+1
|
||||||
flutter_material_design_icons: ^1.1.7447
|
flutter_material_design_icons: ^1.1.7447
|
||||||
@@ -42,16 +37,6 @@ dependencies:
|
|||||||
http: ^1.4.0
|
http: ^1.4.0
|
||||||
intl: ^0.19.0
|
intl: ^0.19.0
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
langchain:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/langchain
|
|
||||||
langchain_openai:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/langchain_openai
|
|
||||||
lexo_rank:
|
lexo_rank:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/FaFre/lexo_rank.git
|
url: https://github.com/FaFre/lexo_rank.git
|
||||||
@@ -64,7 +49,6 @@ dependencies:
|
|||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
pocketbase: ^0.22.0
|
pocketbase: ^0.22.0
|
||||||
provider: ^6.1.5
|
|
||||||
riverpod: ^2.6.1
|
riverpod: ^2.6.1
|
||||||
riverpod_annotation: ^2.6.1
|
riverpod_annotation: ^2.6.1
|
||||||
rss_dart: ^1.0.12
|
rss_dart: ^1.0.12
|
||||||
@@ -77,10 +61,7 @@ dependencies:
|
|||||||
speech_to_text_google_dialog:
|
speech_to_text_google_dialog:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
url: https://github.com/FaFre/speech_to_text_google_dialog.git
|
||||||
sqlite3: ^2.7.5
|
sqlite3_flutter_libs: ^0.5.33
|
||||||
sqlite3_flutter_libs: ^0.5.32
|
|
||||||
sqlite3_vec:
|
|
||||||
git: https://github.com/FaFre/sqlite3_vec.git
|
|
||||||
text_scroll: ^0.2.0
|
text_scroll: ^0.2.0
|
||||||
timeago: ^3.7.1
|
timeago: ^3.7.1
|
||||||
tor:
|
tor:
|
||||||
@@ -94,22 +75,6 @@ dependencies:
|
|||||||
url_launcher: ^6.3.1
|
url_launcher: ^6.3.1
|
||||||
uuid: ^4.5.1
|
uuid: ^4.5.1
|
||||||
|
|
||||||
dependency_overrides:
|
|
||||||
cross_cache:
|
|
||||||
path: ../../flutter_chat_ui/packages/cross_cache
|
|
||||||
flutter_chat_core:
|
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
|
||||||
langchain_core:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/langchain_core
|
|
||||||
tavily_dart:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/tavily_dart
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
build_runner: ^2.4.15
|
build_runner: ^2.4.15
|
||||||
copy_with_extension_gen: ^6.0.1
|
copy_with_extension_gen: ^6.0.1
|
||||||
|
|||||||
@@ -1,20 +1,6 @@
|
|||||||
# melos_managed_dependency_overrides: flutter_mozilla_components,simple_intent_receiver,cross_cache,flutter_chat_core,langchain_core,tavily_dart
|
# melos_managed_dependency_overrides: flutter_mozilla_components,simple_intent_receiver
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
flutter_mozilla_components:
|
flutter_mozilla_components:
|
||||||
path: ../packages/flutter_mozilla_components
|
path: ../packages/flutter_mozilla_components
|
||||||
simple_intent_receiver:
|
simple_intent_receiver:
|
||||||
path: ../packages/simple_intent_receiver
|
path: ../packages/simple_intent_receiver
|
||||||
cross_cache:
|
|
||||||
path: ../../flutter_chat_ui/packages/cross_cache
|
|
||||||
flutter_chat_core:
|
|
||||||
path: ../../flutter_chat_ui/packages/flutter_chat_core
|
|
||||||
langchain_core:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/langchain_core
|
|
||||||
tavily_dart:
|
|
||||||
git:
|
|
||||||
url: https://github.com/FaFre/langchain_dart.git
|
|
||||||
ref: static_objectbox
|
|
||||||
path: packages/tavily_dart
|
|
||||||
|
|||||||
Reference in New Issue
Block a user