Replace ObjectBox with Isar
This commit is contained in:
parent
3eca6e44a7
commit
22fd204e11
14 changed files with 879 additions and 348 deletions
|
@ -10,9 +10,9 @@ import 'package:photos/core/constants.dart';
|
|||
import 'package:photos/core/error-reporting/super_logging.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/db/collections_db.dart';
|
||||
import "package:photos/db/embeddings_db.dart";
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/db/memories_db.dart';
|
||||
import "package:photos/db/object_box.dart";
|
||||
import 'package:photos/db/public_keys_db.dart';
|
||||
import 'package:photos/db/trash_db.dart';
|
||||
import 'package:photos/db/upload_locks_db.dart';
|
||||
|
@ -159,7 +159,7 @@ class Configuration {
|
|||
_secretKey = null;
|
||||
await FilesDB.instance.clearTable();
|
||||
SemanticSearchService.instance.hasInitialized
|
||||
? await ObjectBox.instance.clearTable()
|
||||
? await EmbeddingsDB.instance.clearTable()
|
||||
: null;
|
||||
await CollectionsDB.instance.clearTable();
|
||||
await MemoriesDB.instance.clearTable();
|
||||
|
|
55
lib/db/embeddings_db.dart
Normal file
55
lib/db/embeddings_db.dart
Normal file
|
@ -0,0 +1,55 @@
|
|||
import "package:isar/isar.dart";
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import "package:photos/models/embedding.dart";
|
||||
|
||||
class EmbeddingsDB {
|
||||
late final Isar _isar;
|
||||
|
||||
EmbeddingsDB._privateConstructor();
|
||||
|
||||
static final EmbeddingsDB instance = EmbeddingsDB._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
_isar = await Isar.open(
|
||||
[EmbeddingSchema],
|
||||
directory: dir.path,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> clearTable() async {
|
||||
await _isar.clear();
|
||||
}
|
||||
|
||||
Stream<List<Embedding>> getStream(Model model) {
|
||||
return _isar.embeddings.filter().modelEqualTo(model).watch();
|
||||
}
|
||||
|
||||
Future<List<Embedding>> getAll(Model model) async {
|
||||
return _isar.embeddings.filter().modelEqualTo(model).findAll();
|
||||
}
|
||||
|
||||
Future<void> put(Embedding embedding) {
|
||||
return _isar.writeTxn(() async {
|
||||
await _isar.embeddings.put(embedding);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> putMany(List<Embedding> embeddings) {
|
||||
return _isar.writeTxn(() async {
|
||||
await _isar.embeddings.putAll(embeddings);
|
||||
});
|
||||
}
|
||||
|
||||
Future<List<Embedding>> getUnsyncedEmbeddings() async {
|
||||
return await _isar.embeddings.filter().updationTimeEqualTo(null).findAll();
|
||||
}
|
||||
|
||||
Future<void> deleteAllForModel(Model model) async {
|
||||
await _isar.writeTxn(() async {
|
||||
final embeddings =
|
||||
await _isar.embeddings.filter().modelEqualTo(model).findAll();
|
||||
await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList());
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import 'package:path/path.dart' as p;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import "package:photos/models/embedding.dart";
|
||||
import "package:photos/objectbox.g.dart"; // created by `flutter pub run build_runner build`
|
||||
|
||||
class ObjectBox {
|
||||
/// The Store of this app.
|
||||
late final Store store;
|
||||
|
||||
ObjectBox._privateConstructor();
|
||||
|
||||
static final ObjectBox instance = ObjectBox._privateConstructor();
|
||||
|
||||
Future<void> init() async {
|
||||
final docsDir = await getApplicationDocumentsDirectory();
|
||||
// Future<Store> openStore() {...} is defined in the generated objectbox.g.dart
|
||||
store = await openStore(directory: p.join(docsDir.path, "object-box-store"));
|
||||
}
|
||||
|
||||
Future<void> clearTable() async {
|
||||
getEmbeddingBox().removeAll();
|
||||
}
|
||||
|
||||
Box<Embedding> getEmbeddingBox() {
|
||||
return store.box<Embedding>();
|
||||
}
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
import "dart:convert";
|
||||
|
||||
import "package:objectbox/objectbox.dart";
|
||||
import "package:isar/isar.dart";
|
||||
|
||||
@Entity()
|
||||
part 'embedding.g.dart';
|
||||
|
||||
@collection
|
||||
class Embedding {
|
||||
@Id(assignable: true)
|
||||
Id id = Isar.autoIncrement; // you can also use id = null to auto increment
|
||||
final int fileID;
|
||||
final String model;
|
||||
@enumerated
|
||||
final Model model;
|
||||
final List<double> embedding;
|
||||
int? updationTime;
|
||||
|
||||
|
@ -25,3 +28,34 @@ class Embedding {
|
|||
return jsonEncode(embedding);
|
||||
}
|
||||
}
|
||||
|
||||
enum Model {
|
||||
onnxClip,
|
||||
ggmlClip,
|
||||
}
|
||||
|
||||
extension ModelExtension on Model {
|
||||
String get name => serialize(this);
|
||||
}
|
||||
|
||||
String serialize(Model model) {
|
||||
switch (model) {
|
||||
case Model.onnxClip:
|
||||
return 'onnx-clip';
|
||||
case Model.ggmlClip:
|
||||
return 'ggml-clip';
|
||||
default:
|
||||
throw Exception('$model is not a valid Model');
|
||||
}
|
||||
}
|
||||
|
||||
Model deserialize(String model) {
|
||||
switch (model) {
|
||||
case 'onnx-clip':
|
||||
return Model.onnxClip;
|
||||
case 'ggml-clip':
|
||||
return Model.ggmlClip;
|
||||
default:
|
||||
throw Exception('$model is not a valid Model');
|
||||
}
|
||||
}
|
||||
|
|
756
lib/models/embedding.g.dart
Normal file
756
lib/models/embedding.g.dart
Normal file
|
@ -0,0 +1,756 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'embedding.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// IsarCollectionGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
|
||||
|
||||
extension GetEmbeddingCollection on Isar {
|
||||
IsarCollection<Embedding> get embeddings => this.collection();
|
||||
}
|
||||
|
||||
const EmbeddingSchema = CollectionSchema(
|
||||
name: r'Embedding',
|
||||
id: -8064100183150254587,
|
||||
properties: {
|
||||
r'embedding': PropertySchema(
|
||||
id: 0,
|
||||
name: r'embedding',
|
||||
type: IsarType.doubleList,
|
||||
),
|
||||
r'fileID': PropertySchema(
|
||||
id: 1,
|
||||
name: r'fileID',
|
||||
type: IsarType.long,
|
||||
),
|
||||
r'model': PropertySchema(
|
||||
id: 2,
|
||||
name: r'model',
|
||||
type: IsarType.byte,
|
||||
enumMap: _EmbeddingmodelEnumValueMap,
|
||||
),
|
||||
r'updationTime': PropertySchema(
|
||||
id: 3,
|
||||
name: r'updationTime',
|
||||
type: IsarType.long,
|
||||
)
|
||||
},
|
||||
estimateSize: _embeddingEstimateSize,
|
||||
serialize: _embeddingSerialize,
|
||||
deserialize: _embeddingDeserialize,
|
||||
deserializeProp: _embeddingDeserializeProp,
|
||||
idName: r'id',
|
||||
indexes: {},
|
||||
links: {},
|
||||
embeddedSchemas: {},
|
||||
getId: _embeddingGetId,
|
||||
getLinks: _embeddingGetLinks,
|
||||
attach: _embeddingAttach,
|
||||
version: '3.1.0+1',
|
||||
);
|
||||
|
||||
int _embeddingEstimateSize(
|
||||
Embedding object,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
var bytesCount = offsets.last;
|
||||
bytesCount += 3 + object.embedding.length * 8;
|
||||
return bytesCount;
|
||||
}
|
||||
|
||||
void _embeddingSerialize(
|
||||
Embedding object,
|
||||
IsarWriter writer,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
writer.writeDoubleList(offsets[0], object.embedding);
|
||||
writer.writeLong(offsets[1], object.fileID);
|
||||
writer.writeByte(offsets[2], object.model.index);
|
||||
writer.writeLong(offsets[3], object.updationTime);
|
||||
}
|
||||
|
||||
Embedding _embeddingDeserialize(
|
||||
Id id,
|
||||
IsarReader reader,
|
||||
List<int> offsets,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
final object = Embedding(
|
||||
embedding: reader.readDoubleList(offsets[0]) ?? [],
|
||||
fileID: reader.readLong(offsets[1]),
|
||||
model: _EmbeddingmodelValueEnumMap[reader.readByteOrNull(offsets[2])] ??
|
||||
Model.onnxClip,
|
||||
updationTime: reader.readLongOrNull(offsets[3]),
|
||||
);
|
||||
object.id = id;
|
||||
return object;
|
||||
}
|
||||
|
||||
P _embeddingDeserializeProp<P>(
|
||||
IsarReader reader,
|
||||
int propertyId,
|
||||
int offset,
|
||||
Map<Type, List<int>> allOffsets,
|
||||
) {
|
||||
switch (propertyId) {
|
||||
case 0:
|
||||
return (reader.readDoubleList(offset) ?? []) as P;
|
||||
case 1:
|
||||
return (reader.readLong(offset)) as P;
|
||||
case 2:
|
||||
return (_EmbeddingmodelValueEnumMap[reader.readByteOrNull(offset)] ??
|
||||
Model.onnxClip) as P;
|
||||
case 3:
|
||||
return (reader.readLongOrNull(offset)) as P;
|
||||
default:
|
||||
throw IsarError('Unknown property with id $propertyId');
|
||||
}
|
||||
}
|
||||
|
||||
const _EmbeddingmodelEnumValueMap = {
|
||||
'onnxClip': 0,
|
||||
'ggmlClip': 1,
|
||||
};
|
||||
const _EmbeddingmodelValueEnumMap = {
|
||||
0: Model.onnxClip,
|
||||
1: Model.ggmlClip,
|
||||
};
|
||||
|
||||
Id _embeddingGetId(Embedding object) {
|
||||
return object.id;
|
||||
}
|
||||
|
||||
List<IsarLinkBase<dynamic>> _embeddingGetLinks(Embedding object) {
|
||||
return [];
|
||||
}
|
||||
|
||||
void _embeddingAttach(IsarCollection<dynamic> col, Id id, Embedding object) {
|
||||
object.id = id;
|
||||
}
|
||||
|
||||
extension EmbeddingQueryWhereSort
|
||||
on QueryBuilder<Embedding, Embedding, QWhere> {
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhere> anyId() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(const IdWhereClause.any());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQueryWhere
|
||||
on QueryBuilder<Embedding, Embedding, QWhereClause> {
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhereClause> idEqualTo(Id id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: id,
|
||||
upper: id,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhereClause> idNotEqualTo(Id id) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
if (query.whereSort == Sort.asc) {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||
);
|
||||
} else {
|
||||
return query
|
||||
.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: false),
|
||||
)
|
||||
.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: false),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhereClause> idGreaterThan(Id id,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.greaterThan(lower: id, includeLower: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhereClause> idLessThan(Id id,
|
||||
{bool include = false}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(
|
||||
IdWhereClause.lessThan(upper: id, includeUpper: include),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterWhereClause> idBetween(
|
||||
Id lowerId,
|
||||
Id upperId, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addWhereClause(IdWhereClause.between(
|
||||
lower: lowerId,
|
||||
includeLower: includeLower,
|
||||
upper: upperId,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQueryFilter
|
||||
on QueryBuilder<Embedding, Embedding, QFilterCondition> {
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingElementEqualTo(
|
||||
double value, {
|
||||
double epsilon = Query.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'embedding',
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingElementGreaterThan(
|
||||
double value, {
|
||||
bool include = false,
|
||||
double epsilon = Query.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'embedding',
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingElementLessThan(
|
||||
double value, {
|
||||
bool include = false,
|
||||
double epsilon = Query.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'embedding',
|
||||
value: value,
|
||||
epsilon: epsilon,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingElementBetween(
|
||||
double lower,
|
||||
double upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
double epsilon = Query.epsilon,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'embedding',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
epsilon: epsilon,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingLengthEqualTo(int length) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
length,
|
||||
true,
|
||||
length,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> embeddingIsEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingIsNotEmpty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
0,
|
||||
false,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingLengthLessThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
0,
|
||||
true,
|
||||
length,
|
||||
include,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingLengthGreaterThan(
|
||||
int length, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
length,
|
||||
include,
|
||||
999999,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
embeddingLengthBetween(
|
||||
int lower,
|
||||
int upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.listLength(
|
||||
r'embedding',
|
||||
lower,
|
||||
includeLower,
|
||||
upper,
|
||||
includeUpper,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDEqualTo(
|
||||
int value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'fileID',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDGreaterThan(
|
||||
int value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'fileID',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDLessThan(
|
||||
int value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'fileID',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> fileIDBetween(
|
||||
int lower,
|
||||
int upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'fileID',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idEqualTo(
|
||||
Id value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idGreaterThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idLessThan(
|
||||
Id value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'id',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> idBetween(
|
||||
Id lower,
|
||||
Id upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'id',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelEqualTo(
|
||||
Model value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'model',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelGreaterThan(
|
||||
Model value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'model',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelLessThan(
|
||||
Model value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'model',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> modelBetween(
|
||||
Model lower,
|
||||
Model upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'model',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
updationTimeIsNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNull(
|
||||
property: r'updationTime',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
updationTimeIsNotNull() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(const FilterCondition.isNotNull(
|
||||
property: r'updationTime',
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> updationTimeEqualTo(
|
||||
int? value) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.equalTo(
|
||||
property: r'updationTime',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
updationTimeGreaterThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.greaterThan(
|
||||
include: include,
|
||||
property: r'updationTime',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition>
|
||||
updationTimeLessThan(
|
||||
int? value, {
|
||||
bool include = false,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.lessThan(
|
||||
include: include,
|
||||
property: r'updationTime',
|
||||
value: value,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterFilterCondition> updationTimeBetween(
|
||||
int? lower,
|
||||
int? upper, {
|
||||
bool includeLower = true,
|
||||
bool includeUpper = true,
|
||||
}) {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addFilterCondition(FilterCondition.between(
|
||||
property: r'updationTime',
|
||||
lower: lower,
|
||||
includeLower: includeLower,
|
||||
upper: upper,
|
||||
includeUpper: includeUpper,
|
||||
));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQueryObject
|
||||
on QueryBuilder<Embedding, Embedding, QFilterCondition> {}
|
||||
|
||||
extension EmbeddingQueryLinks
|
||||
on QueryBuilder<Embedding, Embedding, QFilterCondition> {}
|
||||
|
||||
extension EmbeddingQuerySortBy on QueryBuilder<Embedding, Embedding, QSortBy> {
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByFileID() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'fileID', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByFileIDDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'fileID', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByModel() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'model', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByModelDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'model', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByUpdationTime() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'updationTime', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> sortByUpdationTimeDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'updationTime', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQuerySortThenBy
|
||||
on QueryBuilder<Embedding, Embedding, QSortThenBy> {
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByFileID() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'fileID', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByFileIDDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'fileID', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenById() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByIdDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'id', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByModel() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'model', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByModelDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'model', Sort.desc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByUpdationTime() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'updationTime', Sort.asc);
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QAfterSortBy> thenByUpdationTimeDesc() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addSortBy(r'updationTime', Sort.desc);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQueryWhereDistinct
|
||||
on QueryBuilder<Embedding, Embedding, QDistinct> {
|
||||
QueryBuilder<Embedding, Embedding, QDistinct> distinctByEmbedding() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'embedding');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QDistinct> distinctByFileID() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'fileID');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QDistinct> distinctByModel() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'model');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Embedding, QDistinct> distinctByUpdationTime() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addDistinctBy(r'updationTime');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
extension EmbeddingQueryProperty
|
||||
on QueryBuilder<Embedding, Embedding, QQueryProperty> {
|
||||
QueryBuilder<Embedding, int, QQueryOperations> idProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'id');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, List<double>, QQueryOperations> embeddingProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'embedding');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, int, QQueryOperations> fileIDProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'fileID');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, Model, QQueryOperations> modelProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'model');
|
||||
});
|
||||
}
|
||||
|
||||
QueryBuilder<Embedding, int?, QQueryOperations> updationTimeProperty() {
|
||||
return QueryBuilder.apply(this, (query) {
|
||||
return query.addPropertyName(r'updationTime');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
|
||||
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
|
||||
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
|
||||
"entities": [
|
||||
{
|
||||
"id": "1:4067035246682038114",
|
||||
"lastPropertyId": "4:7974898435327252398",
|
||||
"name": "Embedding",
|
||||
"properties": [
|
||||
{
|
||||
"id": "1:2902120230153008095",
|
||||
"name": "fileID",
|
||||
"type": 6,
|
||||
"flags": 129
|
||||
},
|
||||
{
|
||||
"id": "2:5644004076892986076",
|
||||
"name": "model",
|
||||
"type": 9
|
||||
},
|
||||
{
|
||||
"id": "3:4818114203635230783",
|
||||
"name": "embedding",
|
||||
"type": 29
|
||||
},
|
||||
{
|
||||
"id": "4:7974898435327252398",
|
||||
"name": "updationTime",
|
||||
"type": 6
|
||||
}
|
||||
],
|
||||
"relations": []
|
||||
}
|
||||
],
|
||||
"lastEntityId": "1:4067035246682038114",
|
||||
"lastIndexId": "0:0",
|
||||
"lastRelationId": "0:0",
|
||||
"lastSequenceId": "0:0",
|
||||
"modelVersion": 5,
|
||||
"modelVersionParserMinimum": 5,
|
||||
"retiredEntityUids": [],
|
||||
"retiredIndexUids": [],
|
||||
"retiredPropertyUids": [],
|
||||
"retiredRelationUids": [],
|
||||
"version": 1
|
||||
}
|
|
@ -1,171 +0,0 @@
|
|||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// This code was generated by ObjectBox. To update it run the generator again:
|
||||
// With a Flutter package, run `flutter pub run build_runner build`.
|
||||
// With a Dart package, run `dart run build_runner build`.
|
||||
// See also https://docs.objectbox.io/getting-started#generate-objectbox-code
|
||||
|
||||
// ignore_for_file: camel_case_types, depend_on_referenced_packages
|
||||
// coverage:ignore-file
|
||||
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flat_buffers/flat_buffers.dart' as fb;
|
||||
import 'package:objectbox/internal.dart'; // generated code can access "internal" functionality
|
||||
import 'package:objectbox/objectbox.dart';
|
||||
import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
|
||||
|
||||
import 'package:photos/models/embedding.dart';
|
||||
|
||||
export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file
|
||||
|
||||
final _entities = <ModelEntity>[
|
||||
ModelEntity(
|
||||
id: const IdUid(1, 4067035246682038114),
|
||||
name: 'Embedding',
|
||||
lastPropertyId: const IdUid(4, 7974898435327252398),
|
||||
flags: 0,
|
||||
properties: <ModelProperty>[
|
||||
ModelProperty(
|
||||
id: const IdUid(1, 2902120230153008095),
|
||||
name: 'fileID',
|
||||
type: 6,
|
||||
flags: 129,
|
||||
),
|
||||
ModelProperty(
|
||||
id: const IdUid(2, 5644004076892986076),
|
||||
name: 'model',
|
||||
type: 9,
|
||||
flags: 0,
|
||||
),
|
||||
ModelProperty(
|
||||
id: const IdUid(3, 4818114203635230783),
|
||||
name: 'embedding',
|
||||
type: 29,
|
||||
flags: 0,
|
||||
),
|
||||
ModelProperty(
|
||||
id: const IdUid(4, 7974898435327252398),
|
||||
name: 'updationTime',
|
||||
type: 6,
|
||||
flags: 0,
|
||||
),
|
||||
],
|
||||
relations: <ModelRelation>[],
|
||||
backlinks: <ModelBacklink>[],
|
||||
),
|
||||
];
|
||||
|
||||
/// Shortcut for [Store.new] that passes [getObjectBoxModel] and for Flutter
|
||||
/// apps by default a [directory] using `defaultStoreDirectory()` from the
|
||||
/// ObjectBox Flutter library.
|
||||
///
|
||||
/// Note: for desktop apps it is recommended to specify a unique [directory].
|
||||
///
|
||||
/// See [Store.new] for an explanation of all parameters.
|
||||
Future<Store> openStore({
|
||||
String? directory,
|
||||
int? maxDBSizeInKB,
|
||||
int? fileMode,
|
||||
int? maxReaders,
|
||||
bool queriesCaseSensitiveDefault = true,
|
||||
String? macosApplicationGroup,
|
||||
}) async =>
|
||||
Store(
|
||||
getObjectBoxModel(),
|
||||
directory: directory ?? (await defaultStoreDirectory()).path,
|
||||
maxDBSizeInKB: maxDBSizeInKB,
|
||||
fileMode: fileMode,
|
||||
maxReaders: maxReaders,
|
||||
queriesCaseSensitiveDefault: queriesCaseSensitiveDefault,
|
||||
macosApplicationGroup: macosApplicationGroup,
|
||||
);
|
||||
|
||||
/// Returns the ObjectBox model definition for this project for use with
|
||||
/// [Store.new].
|
||||
ModelDefinition getObjectBoxModel() {
|
||||
final model = ModelInfo(
|
||||
entities: _entities,
|
||||
lastEntityId: const IdUid(1, 4067035246682038114),
|
||||
lastIndexId: const IdUid(0, 0),
|
||||
lastRelationId: const IdUid(0, 0),
|
||||
lastSequenceId: const IdUid(0, 0),
|
||||
retiredEntityUids: const [],
|
||||
retiredIndexUids: const [],
|
||||
retiredPropertyUids: const [],
|
||||
retiredRelationUids: const [],
|
||||
modelVersion: 5,
|
||||
modelVersionParserMinimum: 5,
|
||||
version: 1,
|
||||
);
|
||||
|
||||
final bindings = <Type, EntityDefinition>{
|
||||
Embedding: EntityDefinition<Embedding>(
|
||||
model: _entities[0],
|
||||
toOneRelations: (Embedding object) => [],
|
||||
toManyRelations: (Embedding object) => {},
|
||||
getId: (Embedding object) => object.fileID,
|
||||
setId: (Embedding object, int id) {
|
||||
if (object.fileID != id) {
|
||||
throw ArgumentError('Field Embedding.fileID is read-only '
|
||||
'(final or getter-only) and it was declared to be self-assigned. '
|
||||
'However, the currently inserted object (.fileID=${object.fileID}) '
|
||||
"doesn't match the inserted ID (ID $id). "
|
||||
'You must assign an ID before calling [box.put()].');
|
||||
}
|
||||
},
|
||||
objectToFB: (Embedding object, fb.Builder fbb) {
|
||||
final modelOffset = fbb.writeString(object.model);
|
||||
final embeddingOffset = fbb.writeListFloat64(object.embedding);
|
||||
fbb.startTable(5);
|
||||
fbb.addInt64(0, object.fileID);
|
||||
fbb.addOffset(1, modelOffset);
|
||||
fbb.addOffset(2, embeddingOffset);
|
||||
fbb.addInt64(3, object.updationTime);
|
||||
fbb.finish(fbb.endTable());
|
||||
return object.fileID;
|
||||
},
|
||||
objectFromFB: (Store store, ByteData fbData) {
|
||||
final buffer = fb.BufferContext(fbData);
|
||||
final rootOffset = buffer.derefObject(0);
|
||||
final fileIDParam =
|
||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0);
|
||||
final modelParam = const fb.StringReader(asciiOptimization: true)
|
||||
.vTableGet(buffer, rootOffset, 6, '');
|
||||
final embeddingParam =
|
||||
const fb.ListReader<double>(fb.Float64Reader(), lazy: false)
|
||||
.vTableGet(buffer, rootOffset, 8, []);
|
||||
final updationTimeParam =
|
||||
const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 10);
|
||||
final object = Embedding(
|
||||
fileID: fileIDParam,
|
||||
model: modelParam,
|
||||
embedding: embeddingParam,
|
||||
updationTime: updationTimeParam,
|
||||
);
|
||||
|
||||
return object;
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
return ModelDefinition(model, bindings);
|
||||
}
|
||||
|
||||
/// [Embedding] entity fields to define ObjectBox queries.
|
||||
class Embedding_ {
|
||||
/// see [Embedding.fileID]
|
||||
static final fileID =
|
||||
QueryIntegerProperty<Embedding>(_entities[0].properties[0]);
|
||||
|
||||
/// see [Embedding.model]
|
||||
static final model =
|
||||
QueryStringProperty<Embedding>(_entities[0].properties[1]);
|
||||
|
||||
/// see [Embedding.embedding]
|
||||
static final embedding =
|
||||
QueryDoubleVectorProperty<Embedding>(_entities[0].properties[2]);
|
||||
|
||||
/// see [Embedding.updationTime]
|
||||
static final updationTime =
|
||||
QueryIntegerProperty<Embedding>(_entities[0].properties[3]);
|
||||
}
|
|
@ -5,11 +5,10 @@ import "dart:typed_data";
|
|||
import "package:computer/computer.dart";
|
||||
import "package:logging/logging.dart";
|
||||
import "package:photos/core/network/network.dart";
|
||||
import "package:photos/db/embeddings_db.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/db/object_box.dart";
|
||||
import "package:photos/models/embedding.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/objectbox.g.dart";
|
||||
import "package:photos/services/semantic_search/remote_embedding.dart";
|
||||
import "package:photos/utils/crypto_util.dart";
|
||||
import "package:photos/utils/file_download_util.dart";
|
||||
|
@ -20,7 +19,7 @@ class EmbeddingStore {
|
|||
|
||||
static final EmbeddingStore instance = EmbeddingStore._privateConstructor();
|
||||
|
||||
static const kEmbeddingsSyncTimeKey = "sync_time_embeddings";
|
||||
static const kEmbeddingsSyncTimeKey = "sync_time_embeddings_v2";
|
||||
|
||||
final _logger = Logger("EmbeddingStore");
|
||||
final _dio = NetworkClient.instance.enteDio;
|
||||
|
@ -50,12 +49,7 @@ class EmbeddingStore {
|
|||
}
|
||||
|
||||
Future<void> pushEmbeddings() async {
|
||||
final query = (ObjectBox.instance
|
||||
.getEmbeddingBox()
|
||||
.query(Embedding_.updationTime.isNull()))
|
||||
.build();
|
||||
final pendingItems = query.find();
|
||||
query.close();
|
||||
final pendingItems = await EmbeddingsDB.instance.getUnsyncedEmbeddings();
|
||||
for (final item in pendingItems) {
|
||||
final file = await FilesDB.instance.getAnyUploadedFile(item.fileID);
|
||||
await _pushEmbedding(file!, item);
|
||||
|
@ -63,7 +57,7 @@ class EmbeddingStore {
|
|||
}
|
||||
|
||||
Future<void> storeEmbedding(EnteFile file, Embedding embedding) async {
|
||||
ObjectBox.instance.getEmbeddingBox().put(embedding);
|
||||
await EmbeddingsDB.instance.put(embedding);
|
||||
unawaited(_pushEmbedding(file, embedding));
|
||||
}
|
||||
|
||||
|
@ -82,14 +76,14 @@ class EmbeddingStore {
|
|||
"/embeddings",
|
||||
data: {
|
||||
"fileID": embedding.fileID,
|
||||
"model": embedding.model,
|
||||
"model": embedding.model.name,
|
||||
"encryptedEmbedding": encryptedData,
|
||||
"decryptionHeader": header,
|
||||
},
|
||||
);
|
||||
final updationTime = response.data["updationTime"];
|
||||
embedding.updationTime = updationTime;
|
||||
ObjectBox.instance.getEmbeddingBox().put(embedding);
|
||||
await EmbeddingsDB.instance.put(embedding);
|
||||
} catch (e, s) {
|
||||
_logger.severe(e, s);
|
||||
}
|
||||
|
@ -148,7 +142,7 @@ class EmbeddingStore {
|
|||
},
|
||||
);
|
||||
_logger.info("${embeddings.length} embeddings decoded");
|
||||
await ObjectBox.instance.getEmbeddingBox().putManyAsync(embeddings);
|
||||
await EmbeddingsDB.instance.putMany(embeddings);
|
||||
await _preferences.setInt(
|
||||
kEmbeddingsSyncTimeKey,
|
||||
embeddings.last.updationTime!,
|
||||
|
@ -179,7 +173,7 @@ Future<List<Embedding>> decodeEmbeddings(Map<String, dynamic> args) async {
|
|||
embeddings.add(
|
||||
Embedding(
|
||||
fileID: input.embedding.fileID,
|
||||
model: input.embedding.model,
|
||||
model: deserialize(input.embedding.model),
|
||||
embedding: decodedEmbedding,
|
||||
updationTime: input.embedding.updatedAt,
|
||||
),
|
||||
|
|
|
@ -10,11 +10,6 @@ class GGML extends MLFramework {
|
|||
|
||||
final _computer = Computer.shared();
|
||||
final _logger = Logger("GGML");
|
||||
|
||||
@override
|
||||
String getFrameworkName() {
|
||||
return "ggml";
|
||||
}
|
||||
|
||||
@override
|
||||
String getImageModelRemotePath() {
|
||||
|
|
|
@ -11,9 +11,6 @@ abstract class MLFramework {
|
|||
|
||||
final _logger = Logger("MLFramework");
|
||||
|
||||
/// Returns the name of the framework
|
||||
String getFrameworkName();
|
||||
|
||||
/// Returns the path of the Image Model hosted remotely
|
||||
String getImageModelRemotePath();
|
||||
|
||||
|
|
|
@ -17,11 +17,6 @@ class ONNX extends MLFramework {
|
|||
int _textEncoderAddress = 0;
|
||||
int _imageEncoderAddress = 0;
|
||||
|
||||
@override
|
||||
String getFrameworkName() {
|
||||
return "onnx";
|
||||
}
|
||||
|
||||
@override
|
||||
String getImageModelRemotePath() {
|
||||
return kModelBucketEndpoint + kImageModel;
|
||||
|
|
|
@ -6,15 +6,15 @@ import "package:logging/logging.dart";
|
|||
import "package:photos/core/cache/lru_map.dart";
|
||||
import "package:photos/core/configuration.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/db/embeddings_db.dart";
|
||||
import "package:photos/db/files_db.dart";
|
||||
import "package:photos/db/object_box.dart";
|
||||
import "package:photos/events/diff_sync_complete_event.dart";
|
||||
import 'package:photos/events/embedding_updated_event.dart';
|
||||
import "package:photos/events/file_uploaded_event.dart";
|
||||
import "package:photos/models/embedding.dart";
|
||||
import "package:photos/models/file/file.dart";
|
||||
import "package:photos/objectbox.g.dart";
|
||||
import "package:photos/services/semantic_search/embedding_store.dart";
|
||||
import "package:photos/services/semantic_search/frameworks/ggml.dart";
|
||||
import "package:photos/services/semantic_search/frameworks/ml_framework.dart";
|
||||
import 'package:photos/services/semantic_search/frameworks/onnx/onnx.dart';
|
||||
import "package:photos/utils/local_settings.dart";
|
||||
|
@ -32,11 +32,12 @@ class SemanticSearchService {
|
|||
static const kEmbeddingLength = 512;
|
||||
static const kScoreThreshold = 0.23;
|
||||
static const kShouldPushEmbeddings = true;
|
||||
static const kCurrentModel = Model.onnxClip;
|
||||
|
||||
final _logger = Logger("SemanticSearchService");
|
||||
final _queue = Queue<EnteFile>();
|
||||
final _cachedEmbeddings = <Embedding>[];
|
||||
final _mlFramework = ONNX();
|
||||
final _mlFramework = kCurrentModel == Model.onnxClip ? ONNX() : GGML();
|
||||
final _frameworkInitialization = Completer<void>();
|
||||
|
||||
bool _hasInitialized = false;
|
||||
|
@ -56,9 +57,9 @@ class SemanticSearchService {
|
|||
return;
|
||||
}
|
||||
_hasInitialized = true;
|
||||
await ObjectBox.instance.init();
|
||||
await EmbeddingsDB.instance.init();
|
||||
await EmbeddingStore.instance.init();
|
||||
_setupCachedEmbeddings();
|
||||
await _setupCachedEmbeddings();
|
||||
Bus.instance.on<DiffSyncCompleteEvent>().listen((event) {
|
||||
// Diff sync is complete, we can now pull embeddings from remote
|
||||
unawaited(sync());
|
||||
|
@ -132,29 +133,21 @@ class SemanticSearchService {
|
|||
}
|
||||
|
||||
Future<void> clearIndexes() async {
|
||||
await ObjectBox.instance
|
||||
.getEmbeddingBox()
|
||||
.query(
|
||||
Embedding_.model.equals(
|
||||
_mlFramework.getFrameworkName() + "-" + kModelName,
|
||||
),
|
||||
)
|
||||
.build()
|
||||
.removeAsync();
|
||||
_logger.info("Indexes cleared for ${_mlFramework.getFrameworkName()}");
|
||||
await EmbeddingsDB.instance.deleteAllForModel(kCurrentModel);
|
||||
_logger.info("Indexes cleared for $kCurrentModel");
|
||||
}
|
||||
|
||||
void _setupCachedEmbeddings() {
|
||||
ObjectBox.instance
|
||||
.getEmbeddingBox()
|
||||
.query(
|
||||
Embedding_.model.equals(
|
||||
_mlFramework.getFrameworkName() + "-" + kModelName,
|
||||
),
|
||||
)
|
||||
.watch(triggerImmediately: true)
|
||||
.map((query) => query.find())
|
||||
.listen((embeddings) {
|
||||
Future<void> _setupCachedEmbeddings() async {
|
||||
_logger.info("Setting up cached embeddings");
|
||||
final startTime = DateTime.now();
|
||||
final cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel);
|
||||
final endTime = DateTime.now();
|
||||
_logger.info(
|
||||
"Loading ${cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms",
|
||||
);
|
||||
_cachedEmbeddings.addAll(cachedEmbeddings);
|
||||
_logger.info("Cached embeddings: " + _cachedEmbeddings.length.toString());
|
||||
EmbeddingsDB.instance.getStream(kCurrentModel).listen((embeddings) {
|
||||
_logger.info("Updated embeddings: " + embeddings.length.toString());
|
||||
_cachedEmbeddings.clear();
|
||||
_cachedEmbeddings.addAll(embeddings);
|
||||
|
@ -279,7 +272,7 @@ class SemanticSearchService {
|
|||
|
||||
final embedding = Embedding(
|
||||
fileID: file.uploadedFileID!,
|
||||
model: _mlFramework.getFrameworkName() + "-" + kModelName,
|
||||
model: kCurrentModel,
|
||||
embedding: result,
|
||||
);
|
||||
await EmbeddingStore.instance.storeEmbedding(
|
||||
|
|
40
pubspec.lock
40
pubspec.lock
|
@ -315,14 +315,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
csslib:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -596,14 +588,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
flat_buffers:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flat_buffers
|
||||
sha256: "23e2ced0d8e8ecdffbd9f267f49a668c74438393b9acaeac1c724123e3764263"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
|
@ -1385,30 +1369,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
objectbox:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox
|
||||
sha256: "4b645c71771b87188442143a50c55ab238a8e60fe367b6a0968c0842292ffb30"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
objectbox_flutter_libs:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox_flutter_libs
|
||||
sha256: e9a3d8e3ce0d47d6fc942921ef0444a238cd4258e8fcefe13b994cf984a8bf61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
objectbox_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: objectbox_generator
|
||||
sha256: aaffef7eb51b4d911bb00a7c52b19b55fe3e5a69de8ec56552cf35550a1e9beb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -117,8 +117,6 @@ dependencies:
|
|||
move_to_background: ^1.0.2
|
||||
|
||||
# open_file: ^3.2.1
|
||||
objectbox: ^2.3.1
|
||||
objectbox_flutter_libs: any
|
||||
onnxruntime:
|
||||
git: "https://github.com/ente-io/onnxruntime.git"
|
||||
open_mail_app: ^0.4.5
|
||||
|
@ -196,7 +194,6 @@ dev_dependencies:
|
|||
sdk: flutter
|
||||
isar_generator: ^3.1.0+1
|
||||
json_serializable: ^6.6.1
|
||||
objectbox_generator: any
|
||||
test: ^1.22.0
|
||||
|
||||
flutter_icons:
|
||||
|
|
Loading…
Reference in a new issue