diff --git a/README.md b/README.md index 9c445f7..3543253 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Search across all your Logseq blocks using text embeddings. Instead of matching exact keywords, semantic search finds blocks that are **conceptually similar** to your query. -Blocks are embedded with their full context — page name, page properties, and parent block hierarchy — so searching for a page topic or parent heading surfaces relevant child blocks. +Each block is embedded content-first, followed by its context — page name, page and block properties, and parent block hierarchy — so searching for a page topic or parent heading surfaces relevant child blocks. ## Requirements @@ -89,6 +89,8 @@ Semantic search matches meaning, not exact words. To get the best results: | Batch Size | `50` | Number of texts per API request | | Top K Results | `20` | Maximum number of search results | | Auto-index on Load | `true` | Automatically index when the graph loads | +| Page Properties | `tags, alias, category, type, ...` | Comma-separated page properties to include in embedding context | +| Block Properties | `type, status, priority, tags, ...` | Comma-separated block properties to include in embedding text | ## Support diff --git a/package.json b/package.json index 0c99a55..6db99a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "logseq-plugin-semantic-search", - "version": "0.1.6", + "version": "0.2.0", "description": "Search your Logseq notes by meaning, not just keywords", "main": "dist/index.html", "scripts": { diff --git a/src/indexer.ts b/src/indexer.ts index d8cbb21..1e44992 100644 --- a/src/indexer.ts +++ b/src/indexer.ts @@ -9,7 +9,6 @@ import { getMetadata, setMetadata, getEmbeddingCount, - invalidateEmbeddingCache, } from "./storage"; import { getSettings } from "./settings"; @@ -407,9 +406,6 @@ export async function indexBlocks( await setMetadata("blockCount", count); await setMetadata("lastIndexed", Date.now()); - // Ensure search cache is fresh after indexing - invalidateEmbeddingCache(); - if (!abort.signal.aborted && total > 0) { logseq.UI.showMsg(`Indexed ${total} blocks`); } diff --git a/src/main.ts b/src/main.ts index 1820f87..4beede5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -55,14 +55,18 @@ async function main() { } }); + async function syncGraphName(): Promise { + const graph = await logseq.App.getCurrentGraph(); + if (graph?.name) setGraphName(graph.name); + } + // Auto-index on load const settings = getSettings(); if (settings.autoIndexOnLoad) { // Delay to let Logseq finish loading setTimeout(async () => { try { - const graph = await logseq.App.getCurrentGraph(); - if (graph?.name) setGraphName(graph.name); + await syncGraphName(); await indexBlocks(); } catch (err) { console.error("Auto-indexing failed:", err); @@ -72,8 +76,7 @@ async function main() { // Re-index on graph change logseq.App.onCurrentGraphChanged(async () => { - const graph = await logseq.App.getCurrentGraph(); - if (graph?.name) setGraphName(graph.name); + await syncGraphName(); const s = getSettings(); if (s.autoIndexOnLoad) { setTimeout(() => { diff --git a/src/settings.ts b/src/settings.ts index 06edf9a..094a34b 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -93,16 +93,13 @@ export interface PluginSettings { blockProperties: string; } +const settingsDefaults: PluginSettings = Object.fromEntries( + settingsSchema + .filter((s) => s.type !== "heading") + .map((s) => [s.key, s.default]), +) as unknown as PluginSettings; + export function getSettings(): PluginSettings { const s = logseq.settings as Partial | undefined; - return { - apiEndpoint: s?.apiEndpoint ?? "http://localhost:11434", - apiFormat: s?.apiFormat ?? "ollama", - embeddingModel: s?.embeddingModel ?? "nomic-embed-text", - batchSize: s?.batchSize ?? 50, - topK: s?.topK ?? 20, - autoIndexOnLoad: s?.autoIndexOnLoad ?? true, - pageProperties: s?.pageProperties ?? "tags, alias, category, type, description, summary, author, topic, area, project, status, priority, platform", - blockProperties: s?.blockProperties ?? "type, status, priority, tags, source, url, author", - }; + return { ...settingsDefaults, ...s }; } diff --git a/src/storage.ts b/src/storage.ts index 9e6765e..6d0b5f7 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -122,10 +122,6 @@ export function invalidateEmbeddingCache(): void { embeddingCache = null; } -export function evictEmbeddingCache(): void { - embeddingCache = null; -} - export async function deleteEmbeddings(blockIds: string[]): Promise { if (blockIds.length === 0) return; const db = await openDB(); diff --git a/src/ui.ts b/src/ui.ts index 7f4bf8f..c94b717 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -1,6 +1,6 @@ import { debounce } from "./utils"; import { embedTexts } from "./embeddings"; -import { getCachedEmbeddings, getEmbeddingCount, evictEmbeddingCache } from "./storage"; +import { getCachedEmbeddings, getEmbeddingCount, invalidateEmbeddingCache } from "./storage"; import { searchEmbeddings, type SearchResult } from "./search"; import { indexBlocks, indexingState, acquireSearchPriority, releaseSearchPriority } from "./indexer"; import { getSettings } from "./settings"; @@ -196,7 +196,7 @@ function hideModal(): void { // Evict embedding cache after idle period if (evictTimer) clearTimeout(evictTimer); evictTimer = setTimeout(() => { - evictEmbeddingCache(); + invalidateEmbeddingCache(); evictTimer = undefined; }, CACHE_EVICT_MS); } @@ -427,7 +427,7 @@ export function showModal(): void { evictTimer = undefined; } // Invalidate cache so first search loads fresh from IDB - evictEmbeddingCache(); + invalidateEmbeddingCache(); clearResults(); logseq.showMainUI(); if (indexingState.status === "idle") { diff --git a/styles/search-modal.css b/styles/search-modal.css index 15d5d80..6355dfb 100644 --- a/styles/search-modal.css +++ b/styles/search-modal.css @@ -16,7 +16,7 @@ background: var(--ls-primary-background-color, #fff); color: var(--ls-primary-text-color, #333); border-radius: 8px; - width: 600px; + width: 900px; max-width: 90vw; max-height: 70vh; display: flex;