From 0e600e5f6caa08f9f5bed63df1dd10842e9e7f5a Mon Sep 17 00:00:00 2001 From: Garret Patti <42485635+garretpatti@users.noreply.github.com> Date: Tue, 21 Apr 2026 17:57:52 -0400 Subject: [PATCH 1/2] search mixed few text --- src/app/api/browse/route.ts | 46 ++++++++++++++++++++++-------- src/components/mixed/MixedView.tsx | 11 ++++++- src/types/index.ts | 3 ++ 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/app/api/browse/route.ts b/src/app/api/browse/route.ts index 0433488..da24a11 100644 --- a/src/app/api/browse/route.ts +++ b/src/app/api/browse/route.ts @@ -33,12 +33,34 @@ export async function GET(request: NextRequest) { ? await scanDirectoryRecursive(root, libraryId, subpath) : scanDirectory(root, libraryId, subpath) - // Annotate image files with hasExtractedText, and directories if any descendant has extracted text + // Annotate entries with metadata used by search/filtering in mixed view. const db = getDb() - const rows = db - .prepare('SELECT item_key FROM media_items WHERE library_id = ? AND extracted_text IS NOT NULL') - .all(libraryId) as { item_key: string }[] - const withText = new Set(rows.map((r) => r.item_key)) + const metadataRows = db + .prepare(` + SELECT item_key, user_rating, ai_description, extracted_text, extracted_text_translated + FROM media_items + WHERE library_id = ? + AND ( + user_rating IS NOT NULL + OR ai_description IS NOT NULL + OR extracted_text IS NOT NULL + OR extracted_text_translated IS NOT NULL + ) + `) + .all(libraryId) as { + item_key: string + user_rating: number | null + ai_description: string | null + extracted_text: string | null + extracted_text_translated: string | null + }[] + + const metadataByItemKey = new Map(metadataRows.map((r) => [r.item_key, r])) + const withText = new Set( + metadataRows + .filter((r) => r.extracted_text !== null) + .map((r) => r.item_key) + ) // Build a set of all ancestor directory relative paths that contain at least one item with text // e.g. item_key "lib:mixed_file:manga%2Fch1%2Fp1.jpg" → ancestors "manga", "manga/ch1" @@ -52,19 +74,19 @@ export async function GET(request: NextRequest) { } } - const ratingRows = db - .prepare('SELECT item_key, user_rating FROM media_items WHERE library_id = ? AND user_rating IS NOT NULL') - .all(libraryId) as { item_key: string; user_rating: number }[] - const ratingMap = new Map(ratingRows.map((r) => [r.item_key, r.user_rating])) - listing.entries = listing.entries.map((e) => { if (e.type === 'file') { - const relPath = subpath ? path.join(subpath, e.name) : e.name + // Recursive listing already uses full path from library root in e.name. + const relPath = recursive ? e.name : (subpath ? path.join(subpath, e.name) : e.name) const itemKey = `${libraryId}:mixed_file:${encodeURIComponent(relPath)}` + const metadata = metadataByItemKey.get(itemKey) return { ...e, ...(e.mediaType === 'image' ? { hasExtractedText: withText.has(itemKey) } : {}), - userRating: ratingMap.get(itemKey) ?? null, + userRating: metadata?.user_rating ?? null, + aiDescription: metadata?.ai_description ?? null, + extractedText: metadata?.extracted_text ?? null, + extractedTextTranslated: metadata?.extracted_text_translated ?? null, } } if (e.type === 'directory') { diff --git a/src/components/mixed/MixedView.tsx b/src/components/mixed/MixedView.tsx index bba39d1..542563b 100644 --- a/src/components/mixed/MixedView.tsx +++ b/src/components/mixed/MixedView.tsx @@ -170,7 +170,16 @@ export default function MixedView({ libraryId, libraryName, initialPath, readOnl const sourceEntries = filtersActive ? recursiveEntries : (listing?.entries ?? []) const filteredEntries = useMemo(() => sourceEntries.filter((entry) => { - if (debouncedSearch && !entry.name.toLowerCase().includes(debouncedSearch.toLowerCase())) return false + if (debouncedSearch) { + const q = debouncedSearch.toLowerCase() + const matchesSearch = [ + entry.name, + entry.aiDescription, + entry.extractedText, + entry.extractedTextTranslated, + ].some((field) => field?.toLowerCase().includes(q)) + if (!matchesSearch) return false + } if (selectedTagIds.size > 0 && entry.type !== 'directory') { const entryTags = assignments[itemKeyFor(entry)] ?? [] if (![...selectedTagIds].every((id) => entryTags.includes(id))) return false diff --git a/src/types/index.ts b/src/types/index.ts index f2825f3..c8295a8 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -80,6 +80,9 @@ export interface FileEntry { thumbnailUrl: string | null hasExtractedText?: boolean userRating?: number | null + aiDescription?: string | null + extractedText?: string | null + extractedTextTranslated?: string | null } export interface Movie { -- 2.49.1 From e283d03e95e58050f243b3f5852ffc882f08c633 Mon Sep 17 00:00:00 2001 From: Garret Patti <42485635+garretpatti@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:07:20 -0400 Subject: [PATCH 2/2] update db pragma --- src/lib/db.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib/db.ts b/src/lib/db.ts index a514b28..6389569 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -12,7 +12,12 @@ export function getDb(): Database.Database { _db = new Database(DB_PATH) _db.pragma('journal_mode = WAL') _db.pragma('foreign_keys = ON') + _db.pragma('busy_timeout = 5000') + _db.pragma('synchronous = NORMAL') + _db.pragma('cache_size = -65536') + _db.pragma('wal_autocheckpoint = 1000') initDb(_db) + _db.pragma('wal_checkpoint(PASSIVE)') return _db } @@ -113,6 +118,7 @@ function initDb(db: Database.Database): void { migrateComicsIndex(db) migrateTagMappingsIndexes(db) migrateUserRating(db) + migrateParentKeyItemTypeIndex(db) seedAppSettings(db) } @@ -467,6 +473,13 @@ function migrateTagMappingsIndexes(db: Database.Database): void { `) } +function migrateParentKeyItemTypeIndex(db: Database.Database): void { + db.exec(` + CREATE INDEX IF NOT EXISTS media_items_parent_key_type + ON media_items(parent_key, item_type); + `) +} + function migrateUserRating(db: Database.Database): void { const cols = db.pragma('table_info(media_items)') as { name: string }[] if (!cols.some((c) => c.name === 'user_rating')) { -- 2.49.1