BookmarkBrain / PLAN.md
smlflg's picture
Initial public upload from Projekte/BookmarkBrain
3ac10cb verified
|
Raw History Blame Contribute Delete
12.8 kB

BookmarkBrain β€” Local-First Semantic Bookmark Manager

Vision

591+ Bookmarks in chaotischen Ordnern (15 Ebenen tief!) werden durchsuchbar per natuerlicher Sprache: "der Bash-Cheatsheet von neulich", "AWS Tutorials", "was hatte ich zu Vertical Farming gespeichert?"

Local-first. Keine Cloud. Keine Accounts. Deine Daten bleiben bei dir.


Architecture Overview

+------------------+     HTTP (localhost:7471)     +------------------+
|  Browser Extension| <--------------------------> |  Python Backend  |
|  (Chrome + Firefox)|                             |  (FastAPI)       |
|                    |                             |                  |
|  - Popup UI        |                             |  - REST API      |
|  - Quick Search    |                             |  - Import/Export |
|  - Save Bookmark   |                             |  - Auto-Tagger   |
|  - Content Extract |                             |  - Embedding Gen |
+------------------+                               +------------------+
                                                          |
                                                   +------+------+
                                                   |   SQLite    |
                                                   | + Embeddings|
                                                   | (numpy .npy)|
                                                   +-------------+

Tech Stack

Component Technology Why
Backend Python 3.11+ / FastAPI Async, schnell, Samuels Hauptsprache
Database SQLite (via sqlite3) Local-first, zero config, single file
Embeddings paraphrase-multilingual-MiniLM-L12-v2 384 dims, DE+EN, CPU-fast
Vector Search sqlite-vec (SQLite Extension) Vektor-Suche + SQL in einer DB
Frontend htmx + Jinja2 (Web UI) Wie StudyAgent, bewaehrter Stack
Browser Extension WXT Framework Ein Codebase fuer Chrome + Firefox
Build uv (Python), pnpm (Extension) Schnell, modern

Warum sqlite-vec statt ChromaDB/FAISS/NumPy?

  • Single File: Bookmarks + Vektoren in einer SQLite-DB, kein separates .npy
  • SQL + Vektor kombinierbar: WHERE domain = 'github.com' AND embedding MATCH ?
  • Zero Dependencies: Pure C Extension, pip install sqlite-vec
  • ChromaDB hat 100+ transitive Dependencies, FAISS ist overkill bei 2000 Bookmarks

Warum paraphrase-multilingual-MiniLM-L12-v2?

  • 384 Dimensionen, 118M Parameter, ~450 MB
  • 50+ Sprachen inkl. Deutsch und Englisch
  • ~2000-4000 SΓ€tze/Sekunde auf CPU
  • ONNX-Beschleunigung moeglich (2-3x Speedup)
  • Deine 591 Bookmarks: Embedding in <1 Sekunde

Data Model

CREATE TABLE bookmarks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    url TEXT NOT NULL UNIQUE,
    title TEXT NOT NULL,
    description TEXT DEFAULT '',         -- User-supplied or auto-extracted
    context TEXT DEFAULT '',             -- "Was wollte ich damit?" - User note
    folder_path TEXT DEFAULT '',         -- Original browser folder path
    domain TEXT DEFAULT '',              -- Extracted from URL
    favicon_url TEXT DEFAULT '',

    -- Auto-generated
    auto_tags TEXT DEFAULT '[]',         -- JSON array of AI-generated tags
    user_tags TEXT DEFAULT '[]',         -- JSON array of user-set tags
    content_snippet TEXT DEFAULT '',     -- First ~500 chars of page content

    -- Metadata
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_visited_at TIMESTAMP,
    visit_count INTEGER DEFAULT 0,
    is_dead BOOLEAN DEFAULT FALSE,      -- Link check: 404?

    -- Search
    embedding_text TEXT DEFAULT ''       -- Combined text used for embedding
);

CREATE VIRTUAL TABLE bookmarks_fts USING fts5(
    title, description, context, auto_tags, user_tags, content_snippet,
    content='bookmarks', content_rowid='id'
);

CREATE INDEX idx_bookmarks_domain ON bookmarks(domain);
CREATE INDEX idx_bookmarks_created ON bookmarks(created_at);

Embeddings als sqlite-vec Virtual Table:

CREATE VIRTUAL TABLE vec_bookmarks USING vec0(
    id INTEGER PRIMARY KEY,
    embedding FLOAT[384]
);

Alles in einer einzigen SQLite-Datei: data/bookmarks.db


Features (nach Prioritaet)

Phase 1: Core (Woche 1-2)

  1. Bookmark Import β€” Netscape HTML Parser (deine Datei), Chrome/Firefox JSON
  2. SQLite Storage β€” Schema wie oben, FTS5 fuer Volltextsuche
  3. Embedding Generation β€” Alle Bookmarks embedden bei Import
  4. Semantic Search API β€” GET /api/search?q=bash+cheatsheet β†’ Top-10 Ergebnisse
  5. Web UI β€” FastAPI + htmx, Suchfeld + Ergebnisliste + Tag-Filter
  6. Auto-Tagging β€” Clustering + Keyword-Extraction aus Titel/URL/Domain

Phase 2: Browser Extension (Woche 3)

  1. Extension Popup β€” Suchfeld, zeigt Ergebnisse aus Backend
  2. Quick Save β€” Aktuelle Seite speichern mit einem Klick
  3. Content Extraction β€” Content Script extrahiert Meta-Description, OG-Tags, ersten Absatz
  4. Context Note β€” "Was wollte ich damit?" Textfeld beim Speichern

Phase 3: Intelligence (Woche 4)

  1. Dead Link Checker β€” Async HEAD requests, markiert tote Links
  2. Duplicate Detection β€” Embedding-Similarity > 0.95 β†’ wahrscheinlich Duplikat
  3. Smart Collections β€” Auto-gruppierung via Embedding-Cluster (K-Means)
  4. "Related Bookmarks" β€” Klicke einen Bookmark β†’ zeige aehnliche
  5. Bookmark Health Dashboard β€” Stats, tote Links, Duplikate, ungenutztes

Phase 4: Polish (Woche 5+)

  1. Export β€” Netscape HTML, JSON, Markdown
  2. Keyboard Shortcuts β€” Cmd+K Style Quick-Search
  3. Dark Mode
  4. Backup/Restore
  5. Browser Bookmark Sync β€” Periodic import from browser

Project Structure

~/Projekte/BookmarkBrain/
β”œβ”€β”€ PLAN.md                    # Dieser Plan
β”œβ”€β”€ FOR_SMLFLG.md              # Architektur-Doku
β”œβ”€β”€ pyproject.toml             # Python project config (uv)
β”œβ”€β”€ backend/
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ main.py                # FastAPI app, routes
β”‚   β”œβ”€β”€ database.py            # SQLite connection, schema, migrations
β”‚   β”œβ”€β”€ models.py              # Pydantic models
β”‚   β”œβ”€β”€ search.py              # Semantic + FTS search engine
β”‚   β”œβ”€β”€ embeddings.py          # Model loading, embedding generation
β”‚   β”œβ”€β”€ importer.py            # Netscape HTML / JSON import
β”‚   β”œβ”€β”€ tagger.py              # Auto-tag generation
β”‚   β”œβ”€β”€ health.py              # Dead link checker, duplicate detection
β”‚   └── templates/
β”‚       β”œβ”€β”€ base.html
β”‚       β”œβ”€β”€ index.html          # Main search UI
β”‚       β”œβ”€β”€ bookmark.html       # Single bookmark detail/edit
β”‚       └── components/
β”‚           β”œβ”€β”€ search_results.html
β”‚           β”œβ”€β”€ tag_cloud.html
β”‚           └── stats.html
β”œβ”€β”€ extension/
β”‚   β”œβ”€β”€ wxt.config.ts
β”‚   β”œβ”€β”€ package.json
β”‚   β”œβ”€β”€ entrypoints/
β”‚   β”‚   β”œβ”€β”€ popup/              # Search popup
β”‚   β”‚   β”‚   β”œβ”€β”€ App.tsx
β”‚   β”‚   β”‚   └── main.tsx
β”‚   β”‚   β”œβ”€β”€ background.ts      # Service worker
β”‚   β”‚   └── content.ts         # Page content extraction
β”‚   └── utils/
β”‚       └── api.ts              # Backend API client
β”œβ”€β”€ data/                       # Gitignored
β”‚   └── bookmarks.db            # SQLite + sqlite-vec (alles in einer Datei)
β”œβ”€β”€ scripts/
β”‚   β”œβ”€β”€ import_bookmarks.py    # CLI import tool
β”‚   └── reindex.py             # Rebuild embeddings
└── tests/
    β”œβ”€β”€ test_importer.py
    β”œβ”€β”€ test_search.py
    └── test_tagger.py

API Endpoints

GET  /                          # Web UI (htmx)
GET  /api/search?q=...&limit=10 # Semantic + FTS hybrid search
GET  /api/bookmarks             # List all (paginated)
GET  /api/bookmarks/:id         # Single bookmark
POST /api/bookmarks             # Create bookmark
PUT  /api/bookmarks/:id         # Update (tags, context, etc.)
DELETE /api/bookmarks/:id       # Delete

POST /api/import                # Import from file (Netscape HTML / JSON)
GET  /api/export?format=html    # Export

GET  /api/tags                  # All tags with counts
GET  /api/stats                 # Dashboard stats
GET  /api/health                # Dead link report
GET  /api/duplicates            # Suspected duplicates

POST /api/reindex               # Rebuild all embeddings

Search Algorithm (Hybrid)

import struct

def search(query: str, limit: int = 10) -> list[SearchResult]:
    query_vec = model.encode(query)
    blob = struct.pack(f"{len(query_vec)}f", *query_vec)

    # 1. Semantic Search (sqlite-vec)
    semantic = db.execute("""
        SELECT b.*, v.distance
        FROM vec_bookmarks v
        JOIN bookmarks b ON b.id = v.id
        WHERE v.embedding MATCH ? AND k = ?
        ORDER BY v.distance
    """, (blob, limit * 2)).fetchall()

    # 2. Full-Text Search (SQLite FTS5)
    fts = db.execute(
        "SELECT rowid, rank FROM bookmarks_fts WHERE bookmarks_fts MATCH ?",
        (query,)
    ).fetchall()

    # 3. Hybrid Score: 0.7 * semantic + 0.3 * fts_normalized
    return merge_and_rerank(semantic, fts)[:limit]

Auto-Tagging Strategy

Kein LLM noetig fuer 500-2000 Bookmarks. Stattdessen:

  1. Domain-basierte Tags β€” github.com β†’ "github", youtube.com β†’ "video"
  2. URL-Path Keywords β€” /docs/python/ β†’ "python", "docs"
  3. TF-IDF auf Titel β€” Haeufige Woerter ueber alle Bookmarks = schlechte Tags, seltene aber wiederkehrende Woerter = gute Tags
  4. Folder-Path Tags β€” Software Arc > Linux > Code β†’ "software-architecture", "linux", "code"
  5. Embedding Cluster Labels β€” K-Means Cluster, repraesentativstes Wort pro Cluster

Browser Extension ↔ Backend Communication

Localhost HTTP (nicht Native Messaging):

  • Einfacher zu implementieren
  • Funktioniert identisch in Chrome + Firefox
  • CORS Headers im Backend setzen
  • Backend muss laufen (Systemd Service oder manuell)
// extension/utils/api.ts
const API_BASE = "http://localhost:7471/api";

export async function search(query: string) {
  const res = await fetch(`${API_BASE}/search?q=${encodeURIComponent(query)}`);
  return res.json();
}

export async function saveBookmark(data: BookmarkData) {
  const res = await fetch(`${API_BASE}/bookmarks`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return res.json();
}

Dependencies

Python (backend)

fastapi>=0.115
uvicorn[standard]>=0.30
sentence-transformers>=3.0
sqlite-vec>=0.1
numpy>=1.26
jinja2>=3.1
python-multipart>=0.0.9
httpx>=0.27            # For dead link checking
beautifulsoup4>=4.12   # For HTML bookmark parsing
scikit-learn>=1.4       # For K-Means clustering, TF-IDF

Node (extension)

wxt                    # Browser extension framework
typescript

Differenzierung vs. Prior Art

Feature Raindrop Memex Linkding Hoarder BookmarkBrain
Local-first No Yes Self-host Self-host Yes, zero infra
Semantic Search No No No No (Meilisearch) Yes, local embeddings
AI Tags Pro ($3/mo) No No LLM-based Local, free
"Was wollte ich?" No Annotations No Notes First-class feature
Browser Extension Yes Yes Yes Yes Yes (Chrome+FF)
Setup Complexity None (cloud) Extension only Docker Docker+4 containers pip install + start
Multilingual N/A N/A N/A N/A DE+EN nativ

Kern-Differenzierung:

  1. Semantic Search LOKAL ohne Cloud/LLM-API β€” unique
  2. "Was wollte ich damit?" als First-Class-Feature
  3. Zero-Setup: kein Docker, kein Cloud-Account, pip install && run
  4. Chaotische Ordner β†’ saubere AI-Tags automatisch

Run Commands

# Setup
cd ~/Projekte/BookmarkBrain
uv venv && source .venv/bin/activate
uv pip install -e .

# Import existing bookmarks
python scripts/import_bookmarks.py ~/Downloads/bookmarks_05.03.26.html

# Start backend
uvicorn backend.main:app --reload --port 7471

# Extension dev (separate terminal)
cd extension && pnpm install && pnpm dev

Open Decisions (fuer Samuel)

  1. Port: 7471 OK? (Mnemonic: "BRAIN" auf dem Telefon)
  2. Extension zuerst oder Web UI zuerst? Plan sagt Web UI first.
  3. ONNX-Optimierung sofort oder spaeter? Plan sagt spaeter (Phase 4).
  4. Soll der Backend-Server als Systemd-Service laufen? Oder manuell starten?
  5. Projektname: BookmarkBrain? Oder was anderes?