From 79a6abffb07ea0e1d92962d838c939764028e796 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Wed, 19 Feb 2025 22:54:52 +0100 Subject: [PATCH] delete chat archive --- .../data/database/daos/search.dart | 47 - .../data/database/daos/search.g.dart | 6 - .../chat_archive/data/database/database.dart | 19 - .../chat_archive/data/database/database.drift | 43 - .../data/database/database.g.dart | 806 ------------------ .../features/chat_archive/data/providers.dart | 40 - .../chat_archive/data/providers.g.dart | 30 - .../chat_archive/data/services/file.dart | 75 -- .../chat_archive/data/services/file.g.dart | 30 - .../domain/entities/chat_entity.dart | 25 - .../domain/repositories/archive.dart | 72 -- .../domain/repositories/archive.g.dart | 172 ---- .../domain/repositories/search.dart | 123 --- .../domain/repositories/search.g.dart | 31 - .../presentation/screens/detail.dart | 125 --- .../presentation/screens/list.dart | 82 -- .../presentation/screens/search.dart | 115 --- .../chat_archive/utils/markdown_to_text.dart | 9 - 18 files changed, 1850 deletions(-) delete mode 100644 app/lib/features/chat_archive/data/database/daos/search.dart delete mode 100644 app/lib/features/chat_archive/data/database/daos/search.g.dart delete mode 100644 app/lib/features/chat_archive/data/database/database.dart delete mode 100644 app/lib/features/chat_archive/data/database/database.drift delete mode 100644 app/lib/features/chat_archive/data/database/database.g.dart delete mode 100644 app/lib/features/chat_archive/data/providers.dart delete mode 100644 app/lib/features/chat_archive/data/providers.g.dart delete mode 100644 app/lib/features/chat_archive/data/services/file.dart delete mode 100644 app/lib/features/chat_archive/data/services/file.g.dart delete mode 100644 app/lib/features/chat_archive/domain/entities/chat_entity.dart delete mode 100644 app/lib/features/chat_archive/domain/repositories/archive.dart delete mode 100644 app/lib/features/chat_archive/domain/repositories/archive.g.dart delete mode 100644 app/lib/features/chat_archive/domain/repositories/search.dart delete mode 100644 app/lib/features/chat_archive/domain/repositories/search.g.dart delete mode 100644 app/lib/features/chat_archive/presentation/screens/detail.dart delete mode 100644 app/lib/features/chat_archive/presentation/screens/list.dart delete mode 100644 app/lib/features/chat_archive/presentation/screens/search.dart delete mode 100644 app/lib/features/chat_archive/utils/markdown_to_text.dart diff --git a/app/lib/features/chat_archive/data/database/daos/search.dart b/app/lib/features/chat_archive/data/database/daos/search.dart deleted file mode 100644 index 47c41779..00000000 --- a/app/lib/features/chat_archive/data/database/daos/search.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:lensai/features/chat_archive/data/database/database.dart'; - -part 'search.g.dart'; - -@DriftAccessor() -class SearchDao extends DatabaseAccessor - with _$SearchDaoMixin { - SearchDao(super.db); - - Future indexChats(Iterable chats) { - return db.chat.insertAll(chats); - } - - Future deleteAllChats() { - return db.chat.deleteAll(); - } - - Future deleteChat(String fileName) { - return (db.chat.delete()..where((t) => t.fileName.equals(fileName))).go(); - } - - Future upsertChat(ChatCompanion chat) { - return db.chat.insertOne( - chat, - onConflict: DoUpdate( - (old) => ChatCompanion.custom(content: Variable(chat.content.value)), - ), - ); - } - - Selectable queryChats({ - required String matchPrefix, - required String matchSuffix, - required String ellipsis, - required int snippetLength, - required String searchString, - }) { - return db.chatQuery( - query: db.buildFtsQuery(searchString), - snippetLength: snippetLength, - beforeMatch: matchPrefix, - afterMatch: matchSuffix, - ellipsis: ellipsis, - ); - } -} diff --git a/app/lib/features/chat_archive/data/database/daos/search.g.dart b/app/lib/features/chat_archive/data/database/daos/search.g.dart deleted file mode 100644 index b689a778..00000000 --- a/app/lib/features/chat_archive/data/database/daos/search.g.dart +++ /dev/null @@ -1,6 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'search.dart'; - -// ignore_for_file: type=lint -mixin _$SearchDaoMixin on DatabaseAccessor {} diff --git a/app/lib/features/chat_archive/data/database/database.dart b/app/lib/features/chat_archive/data/database/database.dart deleted file mode 100644 index 7ed123f1..00000000 --- a/app/lib/features/chat_archive/data/database/database.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:lensai/features/chat_archive/data/database/daos/search.dart'; -import 'package:lensai/features/search/domain/fts_tokenizer.dart'; - -part 'database.g.dart'; - -@DriftDatabase(include: {'database.drift'}, daos: [SearchDao]) -class ChatSearchDatabase extends _$ChatSearchDatabase - with TrigramQueryBuilderMixin { - @override - final int schemaVersion = 1; - - @override - final int ftsTokenLimit = 6; - @override - final int ftsMinTokenLength = 3; - - ChatSearchDatabase(super.e); -} diff --git a/app/lib/features/chat_archive/data/database/database.drift b/app/lib/features/chat_archive/data/database/database.drift deleted file mode 100644 index 4afa58f4..00000000 --- a/app/lib/features/chat_archive/data/database/database.drift +++ /dev/null @@ -1,43 +0,0 @@ -CREATE TABLE chat ( - file_name TEXT PRIMARY KEY NOT NULL, - title TEXT NOT NULL, - content TEXT NOT NULL -); - -CREATE VIRTUAL TABLE chat_fts - USING fts5( - title, - content, - content=chat, - tokenize="trigram" - ); - --- Triggers to keep the FTS index up to date. -CREATE TRIGGER chat_after_insert AFTER INSERT ON chat BEGIN - INSERT INTO - chat_fts(rowid, title, content) - VALUES (new.rowid, new.title, new.content); -END; -CREATE TRIGGER chat_after_delete AFTER DELETE ON chat BEGIN - INSERT INTO - chat_fts(chat_fts, rowid, title, content) - VALUES('delete', old.rowid, old.title, old.content); -END; -CREATE TRIGGER chat_after_update AFTER UPDATE ON chat BEGIN - INSERT INTO - chat_fts(chat_fts, rowid, title, content) - VALUES('delete', old.rowid, old.title, old.content); - INSERT INTO - chat_fts(rowid, title, content) - VALUES (new.rowid, new.title, new.content); -END; - -chatQuery: - SELECT - c.file_name, - highlight(chat_fts, 0, :beforeMatch, :afterMatch) AS title, - snippet(chat_fts, 1, :beforeMatch, :afterMatch, :ellipsis, :snippetLength) AS content_snippet - FROM chat_fts(:query) fts - INNER JOIN - chat c ON c.rowid = fts.rowid - ORDER BY RANK; \ No newline at end of file diff --git a/app/lib/features/chat_archive/data/database/database.g.dart b/app/lib/features/chat_archive/data/database/database.g.dart deleted file mode 100644 index 46fed13e..00000000 --- a/app/lib/features/chat_archive/data/database/database.g.dart +++ /dev/null @@ -1,806 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'database.dart'; - -// ignore_for_file: type=lint -class Chat extends Table with TableInfo { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - Chat(this.attachedDatabase, [this._alias]); - late final GeneratedColumn fileName = GeneratedColumn( - 'file_name', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: 'PRIMARY KEY NOT NULL', - ); - late final GeneratedColumn title = GeneratedColumn( - 'title', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - late final GeneratedColumn content = GeneratedColumn( - 'content', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: 'NOT NULL', - ); - @override - List get $columns => [fileName, title, content]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'chat'; - @override - Set get $primaryKey => {fileName}; - @override - ChatData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ChatData( - fileName: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}file_name'], - )!, - title: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}title'], - )!, - content: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}content'], - )!, - ); - } - - @override - Chat createAlias(String alias) { - return Chat(attachedDatabase, alias); - } - - @override - bool get dontWriteConstraints => true; -} - -class ChatData extends DataClass implements Insertable { - final String fileName; - final String title; - final String content; - const ChatData({ - required this.fileName, - required this.title, - required this.content, - }); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['file_name'] = Variable(fileName); - map['title'] = Variable(title); - map['content'] = Variable(content); - return map; - } - - factory ChatData.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return ChatData( - fileName: serializer.fromJson(json['file_name']), - title: serializer.fromJson(json['title']), - content: serializer.fromJson(json['content']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'file_name': serializer.toJson(fileName), - 'title': serializer.toJson(title), - 'content': serializer.toJson(content), - }; - } - - ChatData copyWith({String? fileName, String? title, String? content}) => - ChatData( - fileName: fileName ?? this.fileName, - title: title ?? this.title, - content: content ?? this.content, - ); - ChatData copyWithCompanion(ChatCompanion data) { - return ChatData( - fileName: data.fileName.present ? data.fileName.value : this.fileName, - title: data.title.present ? data.title.value : this.title, - content: data.content.present ? data.content.value : this.content, - ); - } - - @override - String toString() { - return (StringBuffer('ChatData(') - ..write('fileName: $fileName, ') - ..write('title: $title, ') - ..write('content: $content') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(fileName, title, content); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ChatData && - other.fileName == this.fileName && - other.title == this.title && - other.content == this.content); -} - -class ChatCompanion extends UpdateCompanion { - final Value fileName; - final Value title; - final Value content; - final Value rowid; - const ChatCompanion({ - this.fileName = const Value.absent(), - this.title = const Value.absent(), - this.content = const Value.absent(), - this.rowid = const Value.absent(), - }); - ChatCompanion.insert({ - required String fileName, - required String title, - required String content, - this.rowid = const Value.absent(), - }) : fileName = Value(fileName), - title = Value(title), - content = Value(content); - static Insertable custom({ - Expression? fileName, - Expression? title, - Expression? content, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (fileName != null) 'file_name': fileName, - if (title != null) 'title': title, - if (content != null) 'content': content, - if (rowid != null) 'rowid': rowid, - }); - } - - ChatCompanion copyWith({ - Value? fileName, - Value? title, - Value? content, - Value? rowid, - }) { - return ChatCompanion( - fileName: fileName ?? this.fileName, - title: title ?? this.title, - content: content ?? this.content, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (fileName.present) { - map['file_name'] = Variable(fileName.value); - } - if (title.present) { - map['title'] = Variable(title.value); - } - if (content.present) { - map['content'] = Variable(content.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('ChatCompanion(') - ..write('fileName: $fileName, ') - ..write('title: $title, ') - ..write('content: $content, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -class ChatFts extends Table - with TableInfo, VirtualTableInfo { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - ChatFts(this.attachedDatabase, [this._alias]); - late final GeneratedColumn title = GeneratedColumn( - 'title', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: '', - ); - late final GeneratedColumn content = GeneratedColumn( - 'content', - aliasedName, - false, - type: DriftSqlType.string, - requiredDuringInsert: true, - $customConstraints: '', - ); - @override - List get $columns => [title, content]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'chat_fts'; - @override - Set get $primaryKey => const {}; - @override - ChatFt map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return ChatFt( - title: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}title'], - )!, - content: - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}content'], - )!, - ); - } - - @override - ChatFts createAlias(String alias) { - return ChatFts(attachedDatabase, alias); - } - - @override - bool get dontWriteConstraints => true; - @override - String get moduleAndArgs => - 'fts5(title, content, content=chat, tokenize="trigram")'; -} - -class ChatFt extends DataClass implements Insertable { - final String title; - final String content; - const ChatFt({required this.title, required this.content}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['title'] = Variable(title); - map['content'] = Variable(content); - return map; - } - - factory ChatFt.fromJson( - Map json, { - ValueSerializer? serializer, - }) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return ChatFt( - title: serializer.fromJson(json['title']), - content: serializer.fromJson(json['content']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'title': serializer.toJson(title), - 'content': serializer.toJson(content), - }; - } - - ChatFt copyWith({String? title, String? content}) => - ChatFt(title: title ?? this.title, content: content ?? this.content); - ChatFt copyWithCompanion(ChatFtsCompanion data) { - return ChatFt( - title: data.title.present ? data.title.value : this.title, - content: data.content.present ? data.content.value : this.content, - ); - } - - @override - String toString() { - return (StringBuffer('ChatFt(') - ..write('title: $title, ') - ..write('content: $content') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(title, content); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is ChatFt && - other.title == this.title && - other.content == this.content); -} - -class ChatFtsCompanion extends UpdateCompanion { - final Value title; - final Value content; - final Value rowid; - const ChatFtsCompanion({ - this.title = const Value.absent(), - this.content = const Value.absent(), - this.rowid = const Value.absent(), - }); - ChatFtsCompanion.insert({ - required String title, - required String content, - this.rowid = const Value.absent(), - }) : title = Value(title), - content = Value(content); - static Insertable custom({ - Expression? title, - Expression? content, - Expression? rowid, - }) { - return RawValuesInsertable({ - if (title != null) 'title': title, - if (content != null) 'content': content, - if (rowid != null) 'rowid': rowid, - }); - } - - ChatFtsCompanion copyWith({ - Value? title, - Value? content, - Value? rowid, - }) { - return ChatFtsCompanion( - title: title ?? this.title, - content: content ?? this.content, - rowid: rowid ?? this.rowid, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (title.present) { - map['title'] = Variable(title.value); - } - if (content.present) { - map['content'] = Variable(content.value); - } - if (rowid.present) { - map['rowid'] = Variable(rowid.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('ChatFtsCompanion(') - ..write('title: $title, ') - ..write('content: $content, ') - ..write('rowid: $rowid') - ..write(')')) - .toString(); - } -} - -abstract class _$ChatSearchDatabase extends GeneratedDatabase { - _$ChatSearchDatabase(QueryExecutor e) : super(e); - $ChatSearchDatabaseManager get managers => $ChatSearchDatabaseManager(this); - late final Chat chat = Chat(this); - late final ChatFts chatFts = ChatFts(this); - late final Trigger chatAfterInsert = Trigger( - 'CREATE TRIGGER chat_after_insert AFTER INSERT ON chat BEGIN INSERT INTO chat_fts ("rowid", title, content) VALUES (new."rowid", new.title, new.content);END', - 'chat_after_insert', - ); - late final Trigger chatAfterDelete = Trigger( - 'CREATE TRIGGER chat_after_delete AFTER DELETE ON chat BEGIN INSERT INTO chat_fts (chat_fts, "rowid", title, content) VALUES (\'delete\', old."rowid", old.title, old.content);END', - 'chat_after_delete', - ); - late final Trigger chatAfterUpdate = Trigger( - 'CREATE TRIGGER chat_after_update AFTER UPDATE ON chat BEGIN INSERT INTO chat_fts (chat_fts, "rowid", title, content) VALUES (\'delete\', old."rowid", old.title, old.content);INSERT INTO chat_fts ("rowid", title, content) VALUES (new."rowid", new.title, new.content);END', - 'chat_after_update', - ); - late final SearchDao searchDao = SearchDao(this as ChatSearchDatabase); - Selectable chatQuery({ - required String beforeMatch, - required String afterMatch, - required String ellipsis, - required int snippetLength, - required String query, - }) { - return customSelect( - 'SELECT c.file_name, highlight(chat_fts, 0, ?1, ?2) AS title, snippet(chat_fts, 1, ?1, ?2, ?3, ?4) AS content_snippet FROM chat_fts(?5)AS fts INNER JOIN chat AS c ON c."rowid" = fts."rowid" ORDER BY RANK', - variables: [ - Variable(beforeMatch), - Variable(afterMatch), - Variable(ellipsis), - Variable(snippetLength), - Variable(query), - ], - readsFrom: {chat, chatFts}, - ).map( - (QueryRow row) => ChatQueryResult( - fileName: row.read('file_name'), - title: row.readNullable('title'), - contentSnippet: row.readNullable('content_snippet'), - ), - ); - } - - @override - Iterable> get allTables => - allSchemaEntities.whereType>(); - @override - List get allSchemaEntities => [ - chat, - chatFts, - chatAfterInsert, - chatAfterDelete, - chatAfterUpdate, - ]; - @override - StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ - WritePropagation( - on: TableUpdateQuery.onTableName( - 'chat', - limitUpdateKind: UpdateKind.insert, - ), - result: [TableUpdate('chat_fts', kind: UpdateKind.insert)], - ), - WritePropagation( - on: TableUpdateQuery.onTableName( - 'chat', - limitUpdateKind: UpdateKind.delete, - ), - result: [TableUpdate('chat_fts', kind: UpdateKind.insert)], - ), - WritePropagation( - on: TableUpdateQuery.onTableName( - 'chat', - limitUpdateKind: UpdateKind.update, - ), - result: [TableUpdate('chat_fts', kind: UpdateKind.insert)], - ), - ]); -} - -typedef $ChatCreateCompanionBuilder = - ChatCompanion Function({ - required String fileName, - required String title, - required String content, - Value rowid, - }); -typedef $ChatUpdateCompanionBuilder = - ChatCompanion Function({ - Value fileName, - Value title, - Value content, - Value rowid, - }); - -class $ChatFilterComposer extends Composer<_$ChatSearchDatabase, Chat> { - $ChatFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get fileName => $composableBuilder( - column: $table.fileName, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnFilters(column), - ); -} - -class $ChatOrderingComposer extends Composer<_$ChatSearchDatabase, Chat> { - $ChatOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get fileName => $composableBuilder( - column: $table.fileName, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnOrderings(column), - ); -} - -class $ChatAnnotationComposer extends Composer<_$ChatSearchDatabase, Chat> { - $ChatAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get fileName => - $composableBuilder(column: $table.fileName, builder: (column) => column); - - GeneratedColumn get title => - $composableBuilder(column: $table.title, builder: (column) => column); - - GeneratedColumn get content => - $composableBuilder(column: $table.content, builder: (column) => column); -} - -class $ChatTableManager - extends - RootTableManager< - _$ChatSearchDatabase, - Chat, - ChatData, - $ChatFilterComposer, - $ChatOrderingComposer, - $ChatAnnotationComposer, - $ChatCreateCompanionBuilder, - $ChatUpdateCompanionBuilder, - (ChatData, BaseReferences<_$ChatSearchDatabase, Chat, ChatData>), - ChatData, - PrefetchHooks Function() - > { - $ChatTableManager(_$ChatSearchDatabase db, Chat table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: - () => $ChatFilterComposer($db: db, $table: table), - createOrderingComposer: - () => $ChatOrderingComposer($db: db, $table: table), - createComputedFieldComposer: - () => $ChatAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value fileName = const Value.absent(), - Value title = const Value.absent(), - Value content = const Value.absent(), - Value rowid = const Value.absent(), - }) => ChatCompanion( - fileName: fileName, - title: title, - content: content, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String fileName, - required String title, - required String content, - Value rowid = const Value.absent(), - }) => ChatCompanion.insert( - fileName: fileName, - title: title, - content: content, - rowid: rowid, - ), - withReferenceMapper: - (p0) => - p0 - .map( - (e) => ( - e.readTable(table), - BaseReferences(db, table, e), - ), - ) - .toList(), - prefetchHooksCallback: null, - ), - ); -} - -typedef $ChatProcessedTableManager = - ProcessedTableManager< - _$ChatSearchDatabase, - Chat, - ChatData, - $ChatFilterComposer, - $ChatOrderingComposer, - $ChatAnnotationComposer, - $ChatCreateCompanionBuilder, - $ChatUpdateCompanionBuilder, - (ChatData, BaseReferences<_$ChatSearchDatabase, Chat, ChatData>), - ChatData, - PrefetchHooks Function() - >; -typedef $ChatFtsCreateCompanionBuilder = - ChatFtsCompanion Function({ - required String title, - required String content, - Value rowid, - }); -typedef $ChatFtsUpdateCompanionBuilder = - ChatFtsCompanion Function({ - Value title, - Value content, - Value rowid, - }); - -class $ChatFtsFilterComposer extends Composer<_$ChatSearchDatabase, ChatFts> { - $ChatFtsFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnFilters(column), - ); - - ColumnFilters get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnFilters(column), - ); -} - -class $ChatFtsOrderingComposer extends Composer<_$ChatSearchDatabase, ChatFts> { - $ChatFtsOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get title => $composableBuilder( - column: $table.title, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get content => $composableBuilder( - column: $table.content, - builder: (column) => ColumnOrderings(column), - ); -} - -class $ChatFtsAnnotationComposer - extends Composer<_$ChatSearchDatabase, ChatFts> { - $ChatFtsAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get title => - $composableBuilder(column: $table.title, builder: (column) => column); - - GeneratedColumn get content => - $composableBuilder(column: $table.content, builder: (column) => column); -} - -class $ChatFtsTableManager - extends - RootTableManager< - _$ChatSearchDatabase, - ChatFts, - ChatFt, - $ChatFtsFilterComposer, - $ChatFtsOrderingComposer, - $ChatFtsAnnotationComposer, - $ChatFtsCreateCompanionBuilder, - $ChatFtsUpdateCompanionBuilder, - (ChatFt, BaseReferences<_$ChatSearchDatabase, ChatFts, ChatFt>), - ChatFt, - PrefetchHooks Function() - > { - $ChatFtsTableManager(_$ChatSearchDatabase db, ChatFts table) - : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: - () => $ChatFtsFilterComposer($db: db, $table: table), - createOrderingComposer: - () => $ChatFtsOrderingComposer($db: db, $table: table), - createComputedFieldComposer: - () => $ChatFtsAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value title = const Value.absent(), - Value content = const Value.absent(), - Value rowid = const Value.absent(), - }) => ChatFtsCompanion( - title: title, - content: content, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String title, - required String content, - Value rowid = const Value.absent(), - }) => ChatFtsCompanion.insert( - title: title, - content: content, - rowid: rowid, - ), - withReferenceMapper: - (p0) => - p0 - .map( - (e) => ( - e.readTable(table), - BaseReferences(db, table, e), - ), - ) - .toList(), - prefetchHooksCallback: null, - ), - ); -} - -typedef $ChatFtsProcessedTableManager = - ProcessedTableManager< - _$ChatSearchDatabase, - ChatFts, - ChatFt, - $ChatFtsFilterComposer, - $ChatFtsOrderingComposer, - $ChatFtsAnnotationComposer, - $ChatFtsCreateCompanionBuilder, - $ChatFtsUpdateCompanionBuilder, - (ChatFt, BaseReferences<_$ChatSearchDatabase, ChatFts, ChatFt>), - ChatFt, - PrefetchHooks Function() - >; - -class $ChatSearchDatabaseManager { - final _$ChatSearchDatabase _db; - $ChatSearchDatabaseManager(this._db); - $ChatTableManager get chat => $ChatTableManager(_db, _db.chat); - $ChatFtsTableManager get chatFts => $ChatFtsTableManager(_db, _db.chatFts); -} - -class ChatQueryResult { - final String fileName; - final String? title; - final String? contentSnippet; - ChatQueryResult({required this.fileName, this.title, this.contentSnippet}); -} diff --git a/app/lib/features/chat_archive/data/providers.dart b/app/lib/features/chat_archive/data/providers.dart deleted file mode 100644 index a6ef8ab3..00000000 --- a/app/lib/features/chat_archive/data/providers.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:async'; - -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:lensai/features/chat_archive/data/database/database.dart'; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:riverpod/riverpod.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:sqlite3/sqlite3.dart'; -import 'package:sqlite3_flutter_libs/sqlite3_flutter_libs.dart'; -import 'package:universal_io/io.dart'; - -part 'providers.g.dart'; - -@Riverpod() -ChatSearchDatabase chatSearchDatabase(Ref ref) { - final db = ChatSearchDatabase( - LazyDatabase(() async { - // 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.memory(); - }), - ); - - ref.onDispose(() { - unawaited(db.close()); - }); - - return db; -} diff --git a/app/lib/features/chat_archive/data/providers.g.dart b/app/lib/features/chat_archive/data/providers.g.dart deleted file mode 100644 index 076892e7..00000000 --- a/app/lib/features/chat_archive/data/providers.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'providers.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$chatSearchDatabaseHash() => - r'17c972b3da21b6a2365cc5fd56a4eade0ac0a3f3'; - -/// See also [chatSearchDatabase]. -@ProviderFor(chatSearchDatabase) -final chatSearchDatabaseProvider = - AutoDisposeProvider.internal( - chatSearchDatabase, - name: r'chatSearchDatabaseProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatSearchDatabaseHash, - dependencies: null, - allTransitiveDependencies: null, - ); - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -typedef ChatSearchDatabaseRef = AutoDisposeProviderRef; -// 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 diff --git a/app/lib/features/chat_archive/data/services/file.dart b/app/lib/features/chat_archive/data/services/file.dart deleted file mode 100644 index 7d7b44aa..00000000 --- a/app/lib/features/chat_archive/data/services/file.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:path/path.dart' as path; -import 'package:path_provider/path_provider.dart' as path_provider; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:universal_io/io.dart'; -import 'package:watcher/watcher.dart'; - -part 'file.g.dart'; - -@Riverpod() -class ChatArchiveFileService extends _$ChatArchiveFileService { - final Future _archiveDirectoryFuture; - - ChatArchiveFileService() - : _archiveDirectoryFuture = path_provider - .getApplicationDocumentsDirectory() - .then( - (documentDirectory) => Directory( - path.join(documentDirectory.path, 'archive', 'chat'), - ).create(recursive: true), - ); - - Future> list() { - return _archiveDirectoryFuture.then( - (value) => - value - .list() - .where((file) => path.extension(file.path) == '.md') - .toList(), - ); - } - - Future write(String fileName, String contents) async { - final directory = await _archiveDirectoryFuture; - final file = File(path.join(directory.path, fileName)); - - await file.writeAsString(contents, flush: true); - } - - Future read(String fileName) async { - final directory = await _archiveDirectoryFuture; - final file = File(path.join(directory.path, fileName)); - - if (!await file.exists()) { - return null; - } - - return file.readAsString(); - } - - Future delete(String fileName) async { - final directory = await _archiveDirectoryFuture; - final file = File(path.join(directory.path, fileName)); - - if (await file.exists()) { - await file.delete(); - } - } - - Stream _directoryStream() async* { - final watcher = DirectoryWatcher( - await _archiveDirectoryFuture.then( - (archiveDirectory) => archiveDirectory.absolute.path, - ), - ); - - yield* watcher.events.where((event) => path.extension(event.path) == '.md'); - } - - @override - Raw> build() { - //We return a Raw stream here and yield* doesnt support broadcast. - //So it is required to use asBroadcastStream here - return _directoryStream().asBroadcastStream(); - } -} diff --git a/app/lib/features/chat_archive/data/services/file.g.dart b/app/lib/features/chat_archive/data/services/file.g.dart deleted file mode 100644 index dab2ab80..00000000 --- a/app/lib/features/chat_archive/data/services/file.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'file.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$chatArchiveFileServiceHash() => - r'21a60ae20e01cfd4f25f7f744fb9bc0c5a465e09'; - -/// See also [ChatArchiveFileService]. -@ProviderFor(ChatArchiveFileService) -final chatArchiveFileServiceProvider = AutoDisposeNotifierProvider< - ChatArchiveFileService, - Raw> ->.internal( - ChatArchiveFileService.new, - name: r'chatArchiveFileServiceProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatArchiveFileServiceHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$ChatArchiveFileService = AutoDisposeNotifier>>; -// 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 diff --git a/app/lib/features/chat_archive/domain/entities/chat_entity.dart b/app/lib/features/chat_archive/domain/entities/chat_entity.dart deleted file mode 100644 index 91a3a261..00000000 --- a/app/lib/features/chat_archive/domain/entities/chat_entity.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:lensai/extensions/nullable.dart'; - -class ChatEntity { - static final _namePattern = RegExp(r"^(.*?) - (.*?)\.md$"); - - final String fileName; - - final String? name; - final DateTime? dateTime; - - ChatEntity._(this.fileName, {this.name, this.dateTime}); - - factory ChatEntity.fromFileName(String fileName) { - final match = _namePattern.firstMatch(fileName); - - return ChatEntity._( - fileName, - name: match?.group(1), - dateTime: match.mapNotNull((match) => DateTime.tryParse(match.group(2)!)), - ); - } - - @override - String toString() => name ?? fileName; -} diff --git a/app/lib/features/chat_archive/domain/repositories/archive.dart b/app/lib/features/chat_archive/domain/repositories/archive.dart deleted file mode 100644 index a2f5cf69..00000000 --- a/app/lib/features/chat_archive/domain/repositories/archive.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:exceptions/exceptions.dart'; -import 'package:lensai/features/chat_archive/data/services/file.dart'; -import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart'; -import 'package:lensai/features/kagi/data/services/chat.dart'; -import 'package:path/path.dart' as path; -import 'package:riverpod/riverpod.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:rxdart/rxdart.dart'; - -part 'archive.g.dart'; - -@Riverpod() -class ChatArchiveRepository extends _$ChatArchiveRepository { - Future> listArchivedChats() async { - final files = - await ref.read(chatArchiveFileServiceProvider.notifier).list(); - - return files - .map((file) => ChatEntity.fromFileName(path.basename(file.path))) - .toList(); - } - - Future> archiveChat(String fileName, Uri url) async { - final contentsResult = await ref - .read(kagiChatServiceProvider.notifier) - .downloadChat(url); - - return contentsResult.flatMapAsync( - (contents) => ref - .read(chatArchiveFileServiceProvider.notifier) - .write(fileName, contents), - ); - } - - Future> readChat(String fileName) async { - final contentsResult = await Result.fromAsync( - () => ref.read(chatArchiveFileServiceProvider.notifier).read(fileName), - ); - - return contentsResult.fold( - (value) => - (value == null) - ? Result.failure( - ErrorMessage( - source: 'Chat Archive', - message: 'Chat $fileName not found', - ), - ) - : Result.success(value), - onFailure: Result.failure, - ); - } - - @override - Stream> build() async* { - final fileRepository = ref.watch(chatArchiveFileServiceProvider); - - yield* ConcatStream([ - listArchivedChats().asStream(), - fileRepository.asyncMap((_) => listArchivedChats()), - ]); - } -} - -@Riverpod() -Future readArchivedChat(Ref ref, String fileName) async { - final result = await ref - .read(chatArchiveRepositoryProvider.notifier) - .readChat(fileName); - - return result.value; -} diff --git a/app/lib/features/chat_archive/domain/repositories/archive.g.dart b/app/lib/features/chat_archive/domain/repositories/archive.g.dart deleted file mode 100644 index 2cc4037a..00000000 --- a/app/lib/features/chat_archive/domain/repositories/archive.g.dart +++ /dev/null @@ -1,172 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'archive.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$readArchivedChatHash() => r'beeadc38cf71f7dfb77a33a1202c685543a72b24'; - -/// 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 [readArchivedChat]. -@ProviderFor(readArchivedChat) -const readArchivedChatProvider = ReadArchivedChatFamily(); - -/// See also [readArchivedChat]. -class ReadArchivedChatFamily extends Family> { - /// See also [readArchivedChat]. - const ReadArchivedChatFamily(); - - /// See also [readArchivedChat]. - ReadArchivedChatProvider call(String fileName) { - return ReadArchivedChatProvider(fileName); - } - - @override - ReadArchivedChatProvider getProviderOverride( - covariant ReadArchivedChatProvider provider, - ) { - return call(provider.fileName); - } - - static const Iterable? _dependencies = null; - - @override - Iterable? get dependencies => _dependencies; - - static const Iterable? _allTransitiveDependencies = null; - - @override - Iterable? get allTransitiveDependencies => - _allTransitiveDependencies; - - @override - String? get name => r'readArchivedChatProvider'; -} - -/// See also [readArchivedChat]. -class ReadArchivedChatProvider extends AutoDisposeFutureProvider { - /// See also [readArchivedChat]. - ReadArchivedChatProvider(String fileName) - : this._internal( - (ref) => readArchivedChat(ref as ReadArchivedChatRef, fileName), - from: readArchivedChatProvider, - name: r'readArchivedChatProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$readArchivedChatHash, - dependencies: ReadArchivedChatFamily._dependencies, - allTransitiveDependencies: - ReadArchivedChatFamily._allTransitiveDependencies, - fileName: fileName, - ); - - ReadArchivedChatProvider._internal( - super._createNotifier, { - required super.name, - required super.dependencies, - required super.allTransitiveDependencies, - required super.debugGetCreateSourceHash, - required super.from, - required this.fileName, - }) : super.internal(); - - final String fileName; - - @override - Override overrideWith( - FutureOr Function(ReadArchivedChatRef provider) create, - ) { - return ProviderOverride( - origin: this, - override: ReadArchivedChatProvider._internal( - (ref) => create(ref as ReadArchivedChatRef), - from: from, - name: null, - dependencies: null, - allTransitiveDependencies: null, - debugGetCreateSourceHash: null, - fileName: fileName, - ), - ); - } - - @override - AutoDisposeFutureProviderElement createElement() { - return _ReadArchivedChatProviderElement(this); - } - - @override - bool operator ==(Object other) { - return other is ReadArchivedChatProvider && other.fileName == fileName; - } - - @override - int get hashCode { - var hash = _SystemHash.combine(0, runtimeType.hashCode); - hash = _SystemHash.combine(hash, fileName.hashCode); - - return _SystemHash.finish(hash); - } -} - -@Deprecated('Will be removed in 3.0. Use Ref instead') -// ignore: unused_element -mixin ReadArchivedChatRef on AutoDisposeFutureProviderRef { - /// The parameter `fileName` of this provider. - String get fileName; -} - -class _ReadArchivedChatProviderElement - extends AutoDisposeFutureProviderElement - with ReadArchivedChatRef { - _ReadArchivedChatProviderElement(super.provider); - - @override - String get fileName => (origin as ReadArchivedChatProvider).fileName; -} - -String _$chatArchiveRepositoryHash() => - r'1b84b6f26a7c5f6c27874904b893a979a76e1fec'; - -/// See also [ChatArchiveRepository]. -@ProviderFor(ChatArchiveRepository) -final chatArchiveRepositoryProvider = AutoDisposeStreamNotifierProvider< - ChatArchiveRepository, - List ->.internal( - ChatArchiveRepository.new, - name: r'chatArchiveRepositoryProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatArchiveRepositoryHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$ChatArchiveRepository = AutoDisposeStreamNotifier>; -// 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 diff --git a/app/lib/features/chat_archive/domain/repositories/search.dart b/app/lib/features/chat_archive/domain/repositories/search.dart deleted file mode 100644 index 345cd733..00000000 --- a/app/lib/features/chat_archive/domain/repositories/search.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -import 'package:lensai/core/logger.dart'; -import 'package:lensai/features/chat_archive/data/database/database.dart'; -import 'package:lensai/features/chat_archive/data/providers.dart'; -import 'package:lensai/features/chat_archive/data/services/file.dart'; -import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart'; -import 'package:lensai/features/chat_archive/domain/repositories/archive.dart'; -import 'package:lensai/features/chat_archive/utils/markdown_to_text.dart'; -import 'package:path/path.dart' as path; -import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:watcher/watcher.dart'; - -part 'search.g.dart'; - -@Riverpod() -class ChatArchiveSearchRepository extends _$ChatArchiveSearchRepository { - late Completer _populatedCompleter; - late StreamController> _streamController; - - Future _readChat(ChatEntity chat) async { - final contentResult = await ref - .read(chatArchiveRepositoryProvider.notifier) - .readChat(chat.fileName); - - return contentResult.fold( - (content) => ChatCompanion.insert( - fileName: chat.fileName, - title: chat.name!, - content: markdownToText(content), - ), - onFailure: (errorMessage) { - logger.e( - 'Error reading file "${chat.fileName}": ${errorMessage.message}', - error: errorMessage.details, - stackTrace: errorMessage.stackTrace, - ); - - return null; - }, - ); - } - - Future> _availableChats() async { - final availableChats = await ref - .read(chatArchiveRepositoryProvider.notifier) - .listArchivedChats() - .then( - (chats) => Future.wait( - chats.where((chat) => chat.name != null).map(_readChat), - ), - ); - - return availableChats.nonNulls; - } - - Future search( - String input, { - int snippetLength = 120, - String matchPrefix = '***', - String matchSuffix = '***', - String ellipsis = '…', - }) async { - if (input.isNotEmpty) { - await _populatedCompleter.future; - await ref - .read(chatSearchDatabaseProvider) - .searchDao - .queryChats( - searchString: input, - snippetLength: snippetLength, - matchPrefix: matchPrefix, - matchSuffix: matchSuffix, - ellipsis: ellipsis, - ) - .get() - .then((value) { - if (!_streamController.isClosed) { - _streamController.add(value); - } - }); - } - } - - @override - Stream> build() async* { - _populatedCompleter = Completer(); - _streamController = StreamController(); - - final searchDatabase = ref.watch(chatSearchDatabaseProvider); - - // populate with initial chats - await searchDatabase.searchDao.deleteAllChats(); - await searchDatabase.searchDao.indexChats(await _availableChats()); - _populatedCompleter.complete(); - - final changeStreamSubscription = ref - .watch(chatArchiveFileServiceProvider) - .listen((event) async { - final chat = ChatEntity.fromFileName(path.basename(event.path)); - - switch (event.type) { - case ChangeType.ADD: - case ChangeType.MODIFY: - if (chat.name != null) { - final companion = await _readChat(chat); - if (companion != null) { - await searchDatabase.searchDao.upsertChat(companion); - } - } - case ChangeType.REMOVE: - await searchDatabase.searchDao.deleteChat(chat.fileName); - } - }); - - ref.onDispose(() async { - await changeStreamSubscription.cancel(); - await _streamController.close(); - }); - - yield* _streamController.stream; - } -} diff --git a/app/lib/features/chat_archive/domain/repositories/search.g.dart b/app/lib/features/chat_archive/domain/repositories/search.g.dart deleted file mode 100644 index f2da377d..00000000 --- a/app/lib/features/chat_archive/domain/repositories/search.g.dart +++ /dev/null @@ -1,31 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'search.dart'; - -// ************************************************************************** -// RiverpodGenerator -// ************************************************************************** - -String _$chatArchiveSearchRepositoryHash() => - r'407023f1ee8683f21d4263dccc9eab1d7a40278e'; - -/// See also [ChatArchiveSearchRepository]. -@ProviderFor(ChatArchiveSearchRepository) -final chatArchiveSearchRepositoryProvider = AutoDisposeStreamNotifierProvider< - ChatArchiveSearchRepository, - List ->.internal( - ChatArchiveSearchRepository.new, - name: r'chatArchiveSearchRepositoryProvider', - debugGetCreateSourceHash: - const bool.fromEnvironment('dart.vm.product') - ? null - : _$chatArchiveSearchRepositoryHash, - dependencies: null, - allTransitiveDependencies: null, -); - -typedef _$ChatArchiveSearchRepository = - AutoDisposeStreamNotifier>; -// 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 diff --git a/app/lib/features/chat_archive/presentation/screens/detail.dart b/app/lib/features/chat_archive/presentation/screens/detail.dart deleted file mode 100644 index 4ecb2881..00000000 --- a/app/lib/features/chat_archive/presentation/screens/detail.dart +++ /dev/null @@ -1,125 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/core/routing/routes.dart'; -import 'package:lensai/features/chat_archive/data/services/file.dart'; -import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart'; -import 'package:lensai/features/chat_archive/domain/repositories/archive.dart'; -import 'package:lensai/features/chat_archive/utils/markdown_to_text.dart'; -import 'package:lensai/features/geckoview/domain/repositories/tab.dart'; -import 'package:lensai/presentation/widgets/failure_widget.dart'; -import 'package:skeletonizer/skeletonizer.dart'; - -class ChatArchiveDetailScreen extends HookConsumerWidget { - final String fileName; - - const ChatArchiveDetailScreen(this.fileName, {super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final chatAsync = ref.watch(readArchivedChatProvider(fileName)); - - final entity = useMemoized(() => ChatEntity.fromFileName(fileName)); - - return Scaffold( - appBar: AppBar( - title: Text(entity.toString()), - actions: [ - MenuAnchor( - builder: (context, controller, child) { - return IconButton( - onPressed: () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - icon: const Icon(Icons.more_vert), - ); - }, - menuChildren: [ - MenuItemButton( - onPressed: () async { - if (chatAsync.valueOrNull != null) { - await Clipboard.setData( - ClipboardData( - text: await Future( - () => markdownToText(chatAsync.valueOrNull!), - ), - ), - ); - } - }, - leadingIcon: const Icon(MdiIcons.textLong), - child: const Text('Copy as plain text'), - ), - MenuItemButton( - onPressed: () async { - if (chatAsync.valueOrNull != null) { - await Clipboard.setData( - ClipboardData(text: chatAsync.valueOrNull!), - ); - } - }, - // ignore: deprecated_member_use use this icon for now - leadingIcon: const Icon(MdiIcons.languageMarkdown), - child: const Text('Copy as markdown'), - ), - const Divider(), - MenuItemButton( - onPressed: () async { - await ref - .read(chatArchiveFileServiceProvider.notifier) - .delete(fileName); - - if (context.mounted) { - context.pop(); - } - }, - leadingIcon: const Icon(Icons.delete), - child: const Text('Delete'), - ), - ], - ), - ], - ), - body: Skeletonizer( - enabled: chatAsync.isLoading, - justifyMultiLineText: false, - child: chatAsync.when( - data: - (data) => Markdown( - data: data, - selectable: true, - onTapLink: (text, href, title) async { - if (href != null) { - if (Uri.parse(href) case final Uri url) { - await ref - .read(tabRepositoryProvider.notifier) - .addTab(url: url); - - if (context.mounted) { - context.go(BrowserRoute().location); - } - } - } - }, - ), - error: (error, stackTrace) { - return FailureWidget( - title: 'Could not load chat', - exception: error, - onRetry: () => ref.refresh(readArchivedChatProvider(fileName)), - ); - }, - loading: () => const Bone.multiText(lines: 15), - ), - ), - ); - } -} diff --git a/app/lib/features/chat_archive/presentation/screens/list.dart b/app/lib/features/chat_archive/presentation/screens/list.dart deleted file mode 100644 index d225e087..00000000 --- a/app/lib/features/chat_archive/presentation/screens/list.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:fading_scroll/fading_scroll.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/core/routing/routes.dart'; -import 'package:lensai/extensions/date_time.dart'; -import 'package:lensai/extensions/nullable.dart'; -import 'package:lensai/features/chat_archive/domain/repositories/archive.dart'; -import 'package:lensai/presentation/widgets/failure_widget.dart'; -import 'package:skeletonizer/skeletonizer.dart'; - -class ChatArchiveListScreen extends HookConsumerWidget { - const ChatArchiveListScreen({super.key}); - @override - Widget build(BuildContext context, WidgetRef ref) { - final chatsAsync = ref.watch(chatArchiveRepositoryProvider); - - return Scaffold( - appBar: AppBar( - title: const Text('Chat Archive'), - actions: [ - IconButton( - onPressed: () async { - await context.push(ChatArchiveSearchRoute().location); - }, - icon: const Icon(Icons.search), - ), - ], - ), - body: Skeletonizer( - enabled: chatsAsync.isLoading, - child: chatsAsync.when( - data: (chats) { - return FadingScroll( - fadingSize: 25, - builder: (context, controller) { - return ListView.builder( - controller: controller, - itemCount: chats.length, - itemBuilder: (context, index) { - final chat = chats[index]; - - return ListTile( - title: Text(chat.toString()), - subtitle: chat.dateTime.mapNotNull( - (chatTime) => - Text(chatTime.formatWithMinutePrecision()), - ), - onTap: () async { - await context.push( - ChatArchiveDetailRoute( - fileName: chat.fileName, - ).location, - ); - }, - ); - }, - ); - }, - ); - }, - error: (error, stackTrace) { - return FailureWidget( - title: 'Could not load archived chats', - exception: error, - onRetry: () => ref.refresh(chatArchiveRepositoryProvider), - ); - }, - loading: - () => ListView.builder( - itemCount: 3, - itemBuilder: - (context, index) => const ListTile( - title: Bone.text(), - subtitle: Bone.text(), - ), - ), - ), - ), - ); - } -} diff --git a/app/lib/features/chat_archive/presentation/screens/search.dart b/app/lib/features/chat_archive/presentation/screens/search.dart deleted file mode 100644 index 5e96546f..00000000 --- a/app/lib/features/chat_archive/presentation/screens/search.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'dart:async'; - -import 'package:fading_scroll/fading_scroll.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:go_router/go_router.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:lensai/core/routing/routes.dart'; -import 'package:lensai/extensions/date_time.dart'; -import 'package:lensai/features/chat_archive/domain/entities/chat_entity.dart'; -import 'package:lensai/features/chat_archive/domain/repositories/search.dart'; -import 'package:lensai/features/user/domain/providers.dart'; -import 'package:lensai/presentation/hooks/listenable_callback.dart'; -import 'package:lensai/presentation/widgets/failure_widget.dart'; - -class ChatArchiveSearchScreen extends HookConsumerWidget { - const ChatArchiveSearchScreen({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final resultsAsync = ref.watch(chatArchiveSearchRepositoryProvider); - final incognitoEnabled = ref.watch(incognitoModeEnabledProvider); - - final textEditingController = useTextEditingController(); - - useListenableCallback(textEditingController, () async { - unawaited( - ref - .read(chatArchiveSearchRepositoryProvider.notifier) - .search(textEditingController.text), - ); - }); - - return Scaffold( - appBar: AppBar( - title: TextField( - enableIMEPersonalizedLearning: !incognitoEnabled, - controller: textEditingController, - autofocus: true, - autocorrect: false, - decoration: const InputDecoration.collapsed(hintText: 'Search'), - ), - actions: [ - IconButton( - onPressed: () { - if (textEditingController.text.isEmpty) { - context.pop(); - } else { - textEditingController.clear(); - } - }, - icon: const Icon(Icons.clear), - ), - ], - ), - body: resultsAsync.when( - skipLoadingOnReload: true, - data: - (chats) => FadingScroll( - fadingSize: 25, - builder: (context, controller) { - return ListView.builder( - controller: controller, - itemCount: chats.length, - itemBuilder: (context, index) { - final chat = chats[index]; - final chatEntity = ChatEntity.fromFileName(chat.fileName); - - return Card( - clipBehavior: Clip.antiAlias, - child: InkWell( - onTap: () async { - await context.push( - ChatArchiveDetailRoute( - fileName: chat.fileName, - ).location, - ); - }, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MarkdownBody(data: '## ${chat.title}'), - if (chatEntity.dateTime != null) - Text( - chatEntity.dateTime! - .formatWithMinutePrecision(), - ), - const SizedBox(height: 8.0), - if (chat.contentSnippet != null) - MarkdownBody(data: chat.contentSnippet!), - ], - ), - ), - ), - ); - }, - ); - }, - ), - error: - (error, stackTrace) => Center( - child: FailureWidget( - title: 'Chat Search failed', - exception: error, - ), - ), - loading: () => const SizedBox.shrink(), - ), - ); - } -} diff --git a/app/lib/features/chat_archive/utils/markdown_to_text.dart b/app/lib/features/chat_archive/utils/markdown_to_text.dart deleted file mode 100644 index 9a2bed31..00000000 --- a/app/lib/features/chat_archive/utils/markdown_to_text.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:html/parser.dart' as html_parser; -import 'package:markdown/markdown.dart' as md; - -String markdownToText(String markdown) { - final html = md.markdownToHtml(markdown); - final document = html_parser.parse(html); - - return document.body?.text ?? ''; -}