feat: hybrid search via RRF when embeddings are enabled
Combines TF-IDF and semantic search results using Reciprocal Rank Fusion instead of switching between engines. Notes scoring well in both rise to the top; notes found by only one engine are still included if their rank is strong enough. Improves recall for paraphrased queries and precision over embeddings-only mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -118,4 +118,25 @@ Copy `main.js`, `manifest.json`, `styles.css` into `.obsidian/plugins/memex-chat
|
|||||||
|
|
||||||
## Models (SettingsTab.ts)
|
## Models (SettingsTab.ts)
|
||||||
|
|
||||||
Default: `claude-opus-4-6`. Update `MODELS` array and `DEFAULT_SETTINGS.model` when adding new model IDs.
|
Static `MODELS` array (fallback / initial dropdown population):
|
||||||
|
|
||||||
|
| ID | Label |
|
||||||
|
|---|---|
|
||||||
|
| `claude-opus-4-6` | Claude Opus 4.6 (Stärkste) |
|
||||||
|
| `claude-sonnet-4-6` | Claude Sonnet 4.6 (Empfohlen) |
|
||||||
|
| `claude-haiku-4-5-20251001` | Claude Haiku 4.5 (Schnell) |
|
||||||
|
|
||||||
|
Default: `claude-opus-4-6`.
|
||||||
|
|
||||||
|
**"Aktualisieren" button**: calls `ClaudeClient.fetchModels(apiKey)` to fetch the live model list from the Anthropic API and repopulate the dropdown dynamically. This supersedes the static array at runtime. Update `MODELS` and `DEFAULT_SETTINGS.model` only when changing the compile-time fallback.
|
||||||
|
|
||||||
|
## Embedding Models (EmbedSearch.ts)
|
||||||
|
|
||||||
|
`EMBEDDING_MODELS` array exported from `EmbedSearch.ts` and used to populate the embedding model dropdown in settings:
|
||||||
|
|
||||||
|
| ID | Description |
|
||||||
|
|---|---|
|
||||||
|
| `TaylorAI/bge-micro-v2` | BGE Micro v2 — default, 384-dim, fastest |
|
||||||
|
| `Xenova/all-MiniLM-L6-v2` | MiniLM L6 v2 — 384-dim |
|
||||||
|
| `Xenova/multilingual-e5-small` | Multilingual E5 Small — DE/EN |
|
||||||
|
| `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Multilingual MiniLM L12 |
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ Chat with your Obsidian vault using Claude AI. Ask questions about your notes, g
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Semantic vault search** — TF-IDF index over all your notes, no external API needed for retrieval
|
- **Vault search** — TF-IDF index by default; enable local embeddings for hybrid mode (TF-IDF + semantic merged via RRF), fully offline after first model download
|
||||||
- **Local embeddings** — optional on-device semantic search using `@xenova/transformers` (BGE Micro v2), fully offline after first model download
|
|
||||||
- **Related notes sidebar** — panel showing the most similar notes to whatever you have open, ranked by semantic similarity + frontmatter links + shared tags
|
- **Related notes sidebar** — panel showing the most similar notes to whatever you have open, ranked by semantic similarity + frontmatter links + shared tags
|
||||||
- **Auto context** — relevant notes are automatically found and sent to Claude as context
|
- **Auto context** — relevant notes are automatically found and sent to Claude as context
|
||||||
- **Context preview** — see and edit which notes are included before sending
|
- **Context preview** — see which notes are included before sending, or dismiss to send without context
|
||||||
- **`@mention` autocomplete** — reference specific notes directly in your message
|
- **`@mention` autocomplete** — pin specific notes into context directly from the input field
|
||||||
- **Thread history** — chats saved as Markdown in your vault (default: `Calendar/Chat/`)
|
- **Thread history** — chats saved as Markdown in your vault (default: `Calendar/Chat/`)
|
||||||
- **Source links** — every answer shows which notes were used as context
|
- **Source links** — every answer shows which notes were used as context
|
||||||
- **Prompt buttons** — header mode buttons that extend Claude's system prompt (e.g. draft check, monthly review)
|
- **Prompt buttons** — header mode buttons that extend Claude's system prompt (e.g. draft check, monthly review)
|
||||||
@@ -67,8 +66,8 @@ Header buttons that activate a mode by extending Claude's system prompt with the
|
|||||||
When a button is active:
|
When a button is active:
|
||||||
- The file at its configured vault path is appended to the system prompt
|
- The file at its configured vault path is appended to the system prompt
|
||||||
- An optional hint is shown above the input
|
- An optional hint is shown above the input
|
||||||
- If `searchMode: "date"` is set, context retrieval switches from TF-IDF/embeddings to date-based file lookup (useful for monthly review modes)
|
- If `searchMode: "date"` is set, context retrieval switches to date-based file lookup (useful for monthly review modes)
|
||||||
- Auto context retrieval is skipped while any button is active
|
- Auto context retrieval is skipped
|
||||||
|
|
||||||
Configure prompt buttons in **Settings → Prompt Buttons**.
|
Configure prompt buttons in **Settings → Prompt Buttons**.
|
||||||
|
|
||||||
@@ -82,18 +81,18 @@ In settings you can specify a vault note to always append to the system prompt (
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `Memex Chat öffnen` | Open the chat panel |
|
| `Memex Chat öffnen` | Open the chat panel |
|
||||||
| `Verwandte Notizen` | Open the related notes sidebar |
|
| `Verwandte Notizen` | Open the related notes sidebar |
|
||||||
| `Memex Chat: Index neu aufbauen` | Rebuild the TF-IDF search index |
|
| `Memex Chat: Index neu aufbauen` | Rebuild the search index |
|
||||||
| `Memex Chat: Aktive Notiz als Kontext` | Ask Claude about the currently open note |
|
| `Memex Chat: Aktive Notiz als Kontext` | Ask Claude about the currently open note |
|
||||||
|
|
||||||
## Related Notes Sidebar
|
## Related Notes Sidebar
|
||||||
|
|
||||||
Opens in the right sidebar and automatically shows the top 10 most similar notes to the currently active file. Similarity is computed from:
|
Requires embeddings to be enabled. Opens in the right sidebar and automatically shows the top 10 most similar notes to the currently active file. Similarity is computed from:
|
||||||
|
|
||||||
1. **Semantic embedding similarity** (cosine distance on 384-dim vectors)
|
1. **Semantic embedding similarity** (cosine similarity on 384-dim vectors)
|
||||||
2. **+0.15 boost** for notes linked via `contextProperties` frontmatter fields (e.g. `related: [[Note]]`)
|
2. **+0.15 boost** for notes linked via `contextProperties` frontmatter fields (e.g. `related: [[Note]]`)
|
||||||
3. **+0.05 per shared tag** (up to +0.15)
|
3. **+0.05 per shared tag** (up to +0.15)
|
||||||
|
|
||||||
Notes explicitly linked via frontmatter are marked with a **verknüpft** badge.
|
Notes boosted by a frontmatter link are marked with a **verknüpft** badge.
|
||||||
|
|
||||||
## Settings
|
## Settings
|
||||||
|
|
||||||
@@ -102,7 +101,7 @@ Notes explicitly linked via frontmatter are marked with a **verknüpft** badge.
|
|||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| API Key | — | Your Anthropic API key |
|
| API Key | — | Your Anthropic API key |
|
||||||
| Model | Claude Opus 4.5 | Which Claude model to use |
|
| Model | `claude-opus-4-6` | Which Claude model to use. Click **Aktualisieren** to fetch the live model list from the Anthropic API. |
|
||||||
| Max tokens | 8192 | Maximum output tokens per response |
|
| Max tokens | 8192 | Maximum output tokens per response |
|
||||||
| Max context notes | 6 | How many notes to retrieve per query |
|
| Max context notes | 6 | How many notes to retrieve per query |
|
||||||
| Max chars per note | 2500 | How much of each note to include |
|
| Max chars per note | 2500 | How much of each note to include |
|
||||||
@@ -119,11 +118,20 @@ Notes explicitly linked via frontmatter are marked with a **verknüpft** badge.
|
|||||||
|
|
||||||
| Setting | Default | Description |
|
| Setting | Default | Description |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Use embeddings | off | Enable local semantic search instead of TF-IDF |
|
| Use embeddings | off | Enable hybrid search (TF-IDF + semantic, merged via RRF) |
|
||||||
| Embedding model | BGE Micro v2 | ONNX model for local inference |
|
| Embedding model | BGE Micro v2 | ONNX model for local inference |
|
||||||
| Exclude folders | — | Vault folders skipped during embedding |
|
| Exclude folders | — | Vault folders skipped during embedding |
|
||||||
|
|
||||||
When enabled, embeddings are computed locally (no API call) and cached in `<vault>/.memex-chat/embeddings/`. The model (~22 MB) is downloaded once to `<vault>/.memex-chat/models/`. Indexing progress is shown as an Obsidian notice. Obsidian Sync activity is detected automatically — indexing waits until sync is idle before starting.
|
| Model | Notes |
|
||||||
|
|---|---|
|
||||||
|
| `TaylorAI/bge-micro-v2` | Default — fastest, 384-dim |
|
||||||
|
| `Xenova/all-MiniLM-L6-v2` | 384-dim |
|
||||||
|
| `Xenova/multilingual-e5-small` | German + English |
|
||||||
|
| `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | German + English, larger |
|
||||||
|
|
||||||
|
Embeddings are computed locally (no API call) and cached in `<vault>/.memex-chat/embeddings/`. The model (~22 MB) is downloaded once to `<vault>/.memex-chat/models/`. Indexing progress is shown as an Obsidian notice. Obsidian Sync activity is detected automatically — indexing waits until sync is idle before starting.
|
||||||
|
|
||||||
|
Once indexing completes, context retrieval switches to **hybrid mode**: TF-IDF and semantic results are fetched independently then rank-merged via Reciprocal Rank Fusion. Notes that score well in both engines rise to the top; notes found by only one are still included if their rank is strong enough. This catches paraphrased queries that TF-IDF misses and avoids the over-broadness of embeddings alone.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -32667,6 +32667,54 @@ var EmbedSearch = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// src/HybridSearch.ts
|
||||||
|
var RRF_K = 60;
|
||||||
|
var HybridSearch = class {
|
||||||
|
constructor(tfidf, embed) {
|
||||||
|
this.tfidf = tfidf;
|
||||||
|
this.embed = embed;
|
||||||
|
}
|
||||||
|
isIndexed() {
|
||||||
|
return this.embed.isIndexed();
|
||||||
|
}
|
||||||
|
async search(query, topK = 8) {
|
||||||
|
const fetchK = topK * 3;
|
||||||
|
const [tfidfResults, embedResults] = await Promise.all([
|
||||||
|
this.tfidf.search(query, fetchK),
|
||||||
|
this.embed.search(query, fetchK)
|
||||||
|
]);
|
||||||
|
const tfidfRank = /* @__PURE__ */ new Map();
|
||||||
|
tfidfResults.forEach((r, i) => tfidfRank.set(r.file.path, i));
|
||||||
|
const embedRank = /* @__PURE__ */ new Map();
|
||||||
|
embedResults.forEach((r, i) => embedRank.set(r.file.path, i));
|
||||||
|
const tfidfMap = new Map(tfidfResults.map((r) => [r.file.path, r]));
|
||||||
|
const embedMap = new Map(embedResults.map((r) => [r.file.path, r]));
|
||||||
|
const allPaths = /* @__PURE__ */ new Set([
|
||||||
|
...tfidfResults.map((r) => r.file.path),
|
||||||
|
...embedResults.map((r) => r.file.path)
|
||||||
|
]);
|
||||||
|
const scored = [];
|
||||||
|
for (const path3 of allPaths) {
|
||||||
|
const tr = tfidfRank.has(path3) ? 1 / (RRF_K + tfidfRank.get(path3) + 1) : 0;
|
||||||
|
const er = embedRank.has(path3) ? 1 / (RRF_K + embedRank.get(path3) + 1) : 0;
|
||||||
|
scored.push([path3, tr + er]);
|
||||||
|
}
|
||||||
|
scored.sort((a, b) => b[1] - a[1]);
|
||||||
|
return scored.slice(0, topK).map(([path3, score]) => {
|
||||||
|
const t = tfidfMap.get(path3);
|
||||||
|
const e = embedMap.get(path3);
|
||||||
|
const base = t ?? e;
|
||||||
|
return {
|
||||||
|
file: base.file,
|
||||||
|
score,
|
||||||
|
excerpt: t?.excerpt ?? "",
|
||||||
|
title: base.title,
|
||||||
|
linked: t?.linked ?? e?.linked
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// src/ClaudeClient.ts
|
// src/ClaudeClient.ts
|
||||||
var import_obsidian2 = require("obsidian");
|
var import_obsidian2 = require("obsidian");
|
||||||
var ClaudeClient = class {
|
var ClaudeClient = class {
|
||||||
@@ -33299,10 +33347,11 @@ var MemexChatPlugin = class extends import_obsidian5.Plugin {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super(...arguments);
|
super(...arguments);
|
||||||
this.embedSearch = null;
|
this.embedSearch = null;
|
||||||
|
this.hybridSearch = null;
|
||||||
}
|
}
|
||||||
/** Returns the active search engine: EmbedSearch when enabled, else VaultSearch */
|
/** Returns the active search engine: HybridSearch when embeddings are ready, else VaultSearch */
|
||||||
get activeSearch() {
|
get activeSearch() {
|
||||||
return this.embedSearch ?? this.search;
|
return this.hybridSearch ?? this.search;
|
||||||
}
|
}
|
||||||
async onload() {
|
async onload() {
|
||||||
const loaded = await this.loadData();
|
const loaded = await this.loadData();
|
||||||
@@ -33415,6 +33464,7 @@ var MemexChatPlugin = class extends import_obsidian5.Plugin {
|
|||||||
async initEmbedSearch() {
|
async initEmbedSearch() {
|
||||||
if (!this.settings.useEmbeddings) {
|
if (!this.settings.useEmbeddings) {
|
||||||
this.embedSearch = null;
|
this.embedSearch = null;
|
||||||
|
this.hybridSearch = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.embedSearch = new EmbedSearch(this.app, this.settings.embeddingModel);
|
this.embedSearch = new EmbedSearch(this.app, this.settings.embeddingModel);
|
||||||
@@ -33432,6 +33482,8 @@ var MemexChatPlugin = class extends import_obsidian5.Plugin {
|
|||||||
notice.setMessage(`Memex [${modelShort}]: ${done}/${total}${speedStr}${eta}`);
|
notice.setMessage(`Memex [${modelShort}]: ${done}/${total}${speedStr}${eta}`);
|
||||||
};
|
};
|
||||||
this.waitForSyncIdle(notice).then(() => this.embedSearch?.buildIndex()).then(() => {
|
this.waitForSyncIdle(notice).then(() => this.embedSearch?.buildIndex()).then(() => {
|
||||||
|
if (this.embedSearch)
|
||||||
|
this.hybridSearch = new HybridSearch(this.search, this.embedSearch);
|
||||||
notice.setMessage(`\u2713 Memex [${modelShort}]: ${this.app.vault.getMarkdownFiles().length} Notizen eingebettet`);
|
notice.setMessage(`\u2713 Memex [${modelShort}]: ${this.app.vault.getMarkdownFiles().length} Notizen eingebettet`);
|
||||||
setTimeout(() => notice.hide(), 4e3);
|
setTimeout(() => notice.hide(), 4e3);
|
||||||
this.notifyRelatedView();
|
this.notifyRelatedView();
|
||||||
@@ -33450,6 +33502,7 @@ var MemexChatPlugin = class extends import_obsidian5.Plugin {
|
|||||||
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_MEMEX_CHAT);
|
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE_MEMEX_CHAT);
|
||||||
const view = leaves[0]?.view;
|
const view = leaves[0]?.view;
|
||||||
if (this.settings.useEmbeddings && this.embedSearch) {
|
if (this.settings.useEmbeddings && this.embedSearch) {
|
||||||
|
this.hybridSearch = null;
|
||||||
this.embedSearch.onModelStatus = (status) => {
|
this.embedSearch.onModelStatus = (status) => {
|
||||||
if (view)
|
if (view)
|
||||||
view.setStatus(status);
|
view.setStatus(status);
|
||||||
@@ -33462,6 +33515,7 @@ var MemexChatPlugin = class extends import_obsidian5.Plugin {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
await this.embedSearch.buildIndex();
|
await this.embedSearch.buildIndex();
|
||||||
|
this.hybridSearch = new HybridSearch(this.search, this.embedSearch);
|
||||||
this.embedSearch.onProgress = void 0;
|
this.embedSearch.onProgress = void 0;
|
||||||
this.embedSearch.onModelStatus = void 0;
|
this.embedSearch.onModelStatus = void 0;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import type { VaultSearch, SearchResult } from "./VaultSearch";
|
||||||
|
import type { EmbedSearch } from "./EmbedSearch";
|
||||||
|
|
||||||
|
const RRF_K = 60;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combines TF-IDF and embedding search via Reciprocal Rank Fusion.
|
||||||
|
* Runs both engines in parallel; rank-merges results so neither score
|
||||||
|
* space needs normalization. TF-IDF excerpts are preserved in merged output.
|
||||||
|
*/
|
||||||
|
export class HybridSearch {
|
||||||
|
constructor(
|
||||||
|
private tfidf: VaultSearch,
|
||||||
|
private embed: EmbedSearch
|
||||||
|
) {}
|
||||||
|
|
||||||
|
isIndexed(): boolean {
|
||||||
|
return this.embed.isIndexed();
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(query: string, topK = 8): Promise<SearchResult[]> {
|
||||||
|
const fetchK = topK * 3;
|
||||||
|
const [tfidfResults, embedResults] = await Promise.all([
|
||||||
|
this.tfidf.search(query, fetchK),
|
||||||
|
this.embed.search(query, fetchK),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const tfidfRank = new Map<string, number>();
|
||||||
|
tfidfResults.forEach((r, i) => tfidfRank.set(r.file.path, i));
|
||||||
|
|
||||||
|
const embedRank = new Map<string, number>();
|
||||||
|
embedResults.forEach((r, i) => embedRank.set(r.file.path, i));
|
||||||
|
|
||||||
|
const tfidfMap = new Map(tfidfResults.map((r) => [r.file.path, r]));
|
||||||
|
const embedMap = new Map(embedResults.map((r) => [r.file.path, r]));
|
||||||
|
|
||||||
|
const allPaths = new Set<string>([
|
||||||
|
...tfidfResults.map((r) => r.file.path),
|
||||||
|
...embedResults.map((r) => r.file.path),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const scored: Array<[string, number]> = [];
|
||||||
|
for (const path of allPaths) {
|
||||||
|
const tr = tfidfRank.has(path) ? 1 / (RRF_K + tfidfRank.get(path)! + 1) : 0;
|
||||||
|
const er = embedRank.has(path) ? 1 / (RRF_K + embedRank.get(path)! + 1) : 0;
|
||||||
|
scored.push([path, tr + er]);
|
||||||
|
}
|
||||||
|
|
||||||
|
scored.sort((a, b) => b[1] - a[1]);
|
||||||
|
|
||||||
|
return scored.slice(0, topK).map(([path, score]) => {
|
||||||
|
const t = tfidfMap.get(path);
|
||||||
|
const e = embedMap.get(path);
|
||||||
|
const base = t ?? e!;
|
||||||
|
return {
|
||||||
|
file: base.file,
|
||||||
|
score,
|
||||||
|
excerpt: t?.excerpt ?? "",
|
||||||
|
title: base.title,
|
||||||
|
linked: t?.linked ?? e?.linked,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-3
@@ -2,6 +2,7 @@ import { Notice, Plugin, TFile } from "obsidian";
|
|||||||
import { ChatView, VIEW_TYPE_MEMEX_CHAT } from "./ChatView";
|
import { ChatView, VIEW_TYPE_MEMEX_CHAT } from "./ChatView";
|
||||||
import { VaultSearch } from "./VaultSearch";
|
import { VaultSearch } from "./VaultSearch";
|
||||||
import { EmbedSearch } from "./EmbedSearch";
|
import { EmbedSearch } from "./EmbedSearch";
|
||||||
|
import { HybridSearch } from "./HybridSearch";
|
||||||
import { ClaudeClient } from "./ClaudeClient";
|
import { ClaudeClient } from "./ClaudeClient";
|
||||||
import { MemexChatSettingsTab, MemexChatSettings, DEFAULT_SETTINGS } from "./SettingsTab";
|
import { MemexChatSettingsTab, MemexChatSettings, DEFAULT_SETTINGS } from "./SettingsTab";
|
||||||
import { RelatedNotesView, VIEW_TYPE_RELATED } from "./RelatedNotesView";
|
import { RelatedNotesView, VIEW_TYPE_RELATED } from "./RelatedNotesView";
|
||||||
@@ -15,12 +16,13 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
settings!: MemexChatSettings;
|
settings!: MemexChatSettings;
|
||||||
search!: VaultSearch;
|
search!: VaultSearch;
|
||||||
embedSearch: EmbedSearch | null = null;
|
embedSearch: EmbedSearch | null = null;
|
||||||
|
hybridSearch: HybridSearch | null = null;
|
||||||
claude!: ClaudeClient;
|
claude!: ClaudeClient;
|
||||||
data!: PluginData;
|
data!: PluginData;
|
||||||
|
|
||||||
/** Returns the active search engine: EmbedSearch when enabled, else VaultSearch */
|
/** Returns the active search engine: HybridSearch when embeddings are ready, else VaultSearch */
|
||||||
get activeSearch(): VaultSearch | EmbedSearch {
|
get activeSearch(): VaultSearch | HybridSearch {
|
||||||
return this.embedSearch ?? this.search;
|
return this.hybridSearch ?? this.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
async onload(): Promise<void> {
|
async onload(): Promise<void> {
|
||||||
@@ -149,6 +151,7 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
async initEmbedSearch(): Promise<void> {
|
async initEmbedSearch(): Promise<void> {
|
||||||
if (!this.settings.useEmbeddings) {
|
if (!this.settings.useEmbeddings) {
|
||||||
this.embedSearch = null;
|
this.embedSearch = null;
|
||||||
|
this.hybridSearch = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.embedSearch = new EmbedSearch(this.app, this.settings.embeddingModel);
|
this.embedSearch = new EmbedSearch(this.app, this.settings.embeddingModel);
|
||||||
@@ -176,6 +179,7 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
// Wait for Obsidian Sync to finish before starting (avoids embedding stale/partial files)
|
// Wait for Obsidian Sync to finish before starting (avoids embedding stale/partial files)
|
||||||
this.waitForSyncIdle(notice).then(() => this.embedSearch?.buildIndex())
|
this.waitForSyncIdle(notice).then(() => this.embedSearch?.buildIndex())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
if (this.embedSearch) this.hybridSearch = new HybridSearch(this.search, this.embedSearch);
|
||||||
notice.setMessage(`✓ Memex [${modelShort}]: ${this.app.vault.getMarkdownFiles().length} Notizen eingebettet`);
|
notice.setMessage(`✓ Memex [${modelShort}]: ${this.app.vault.getMarkdownFiles().length} Notizen eingebettet`);
|
||||||
setTimeout(() => notice.hide(), 4000);
|
setTimeout(() => notice.hide(), 4000);
|
||||||
this.notifyRelatedView();
|
this.notifyRelatedView();
|
||||||
@@ -199,6 +203,7 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
|
|
||||||
if (this.settings.useEmbeddings && this.embedSearch) {
|
if (this.settings.useEmbeddings && this.embedSearch) {
|
||||||
// Rebuild semantic (embedding) index
|
// Rebuild semantic (embedding) index
|
||||||
|
this.hybridSearch = null;
|
||||||
this.embedSearch.onModelStatus = (status) => {
|
this.embedSearch.onModelStatus = (status) => {
|
||||||
if (view) view.setStatus(status);
|
if (view) view.setStatus(status);
|
||||||
};
|
};
|
||||||
@@ -212,6 +217,7 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
await this.embedSearch.buildIndex();
|
await this.embedSearch.buildIndex();
|
||||||
|
this.hybridSearch = new HybridSearch(this.search, this.embedSearch);
|
||||||
this.embedSearch.onProgress = undefined;
|
this.embedSearch.onProgress = undefined;
|
||||||
this.embedSearch.onModelStatus = undefined;
|
this.embedSearch.onModelStatus = undefined;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user