Initial commit

Photo-based book cataloger with AI identification.
Room → Cabinet → Shelf → Book hierarchy; FastAPI + SQLite backend;
vanilla JS SPA; OpenAI-compatible plugin system for boundary
detection, text recognition, and archive search.
This commit is contained in:
2026-03-09 14:17:13 +03:00
commit 084d1aebd5
64 changed files with 8605 additions and 0 deletions

52
src/logic/archive.py Normal file
View File

@@ -0,0 +1,52 @@
"""Archive search plugin runner."""
import json
import db
from errors import BookNotFoundError
from models import ArchiveSearcherPlugin, BookRow, CandidateRecord
from logic.identification import build_query
def run_archive_searcher(plugin: ArchiveSearcherPlugin, book_id: str) -> BookRow:
"""Run an archive search for a book and merge results into the candidates list.
Args:
plugin: The archive searcher plugin to execute.
book_id: ID of the book to search for.
Returns:
Updated BookRow after merging search results.
Raises:
BookNotFoundError: If book_id does not exist.
"""
with db.transaction() as c:
book = db.get_book(c, book_id)
if not book:
raise BookNotFoundError(book_id)
query = build_query(book)
if not query:
return book
results: list[CandidateRecord] = plugin.search(query)
existing: list[CandidateRecord] = json.loads(book.candidates or "[]")
existing = [cd for cd in existing if cd.get("source") != plugin.plugin_id]
existing.extend(results)
db.set_book_candidates(c, book_id, json.dumps(existing))
updated = db.get_book(c, book_id)
if not updated:
raise BookNotFoundError(book_id)
return updated
def run_archive_searcher_bg(plugin: ArchiveSearcherPlugin, book_id: str) -> None:
"""Run an archive search in fire-and-forget mode; all exceptions are suppressed.
Args:
plugin: The archive searcher plugin to execute.
book_id: ID of the book to search for.
"""
try:
run_archive_searcher(plugin, book_id)
except Exception:
pass