Fix vault index: prevent stuck state and wait for layout ready
- VaultSearch.buildIndex(): wrap body in try/finally so this.indexing is always reset to false even if an unexpected error occurs. Previously any outer exception left indexing=true permanently, blocking all retries. - main.ts: replace unreliable setTimeout(3000) with onLayoutReady() so the index is built only after Obsidian's vault cache is fully resolved. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -510,60 +510,63 @@ var VaultSearch = class {
|
|||||||
this.docVectors.clear();
|
this.docVectors.clear();
|
||||||
this.idf.clear();
|
this.idf.clear();
|
||||||
this.docContents.clear();
|
this.docContents.clear();
|
||||||
const files = this.app.vault.getMarkdownFiles();
|
try {
|
||||||
const total = files.length;
|
const files = this.app.vault.getMarkdownFiles();
|
||||||
const df = /* @__PURE__ */ new Map();
|
const total = files.length;
|
||||||
const tfs = /* @__PURE__ */ new Map();
|
const df = /* @__PURE__ */ new Map();
|
||||||
for (let i = 0; i < files.length; i++) {
|
const tfs = /* @__PURE__ */ new Map();
|
||||||
const file = files[i];
|
for (let i = 0; i < files.length; i++) {
|
||||||
if (this.onProgress && i % 100 === 0)
|
const file = files[i];
|
||||||
this.onProgress(i, total);
|
if (this.onProgress && i % 100 === 0)
|
||||||
try {
|
this.onProgress(i, total);
|
||||||
const raw = await this.app.vault.cachedRead(file);
|
try {
|
||||||
const clean = this.cleanContent(raw);
|
const raw = await this.app.vault.cachedRead(file);
|
||||||
this.docContents.set(file.path, clean);
|
const clean = this.cleanContent(raw);
|
||||||
const tokens = this.tokenize(clean + " " + file.basename);
|
this.docContents.set(file.path, clean);
|
||||||
const tf = /* @__PURE__ */ new Map();
|
const tokens = this.tokenize(clean + " " + file.basename);
|
||||||
for (const t of tokens) {
|
const tf = /* @__PURE__ */ new Map();
|
||||||
tf.set(t, ((_a = tf.get(t)) != null ? _a : 0) + 1);
|
for (const t of tokens) {
|
||||||
}
|
tf.set(t, ((_a = tf.get(t)) != null ? _a : 0) + 1);
|
||||||
const maxTf = Math.max(...tf.values(), 1);
|
}
|
||||||
const normalizedTf = /* @__PURE__ */ new Map();
|
const maxTf = Math.max(...tf.values(), 1);
|
||||||
for (const [t, count] of tf) {
|
const normalizedTf = /* @__PURE__ */ new Map();
|
||||||
normalizedTf.set(t, count / maxTf);
|
for (const [t, count] of tf) {
|
||||||
}
|
normalizedTf.set(t, count / maxTf);
|
||||||
tfs.set(file.path, normalizedTf);
|
}
|
||||||
for (const t of tf.keys()) {
|
tfs.set(file.path, normalizedTf);
|
||||||
df.set(t, ((_b = df.get(t)) != null ? _b : 0) + 1);
|
for (const t of tf.keys()) {
|
||||||
}
|
df.set(t, ((_b = df.get(t)) != null ? _b : 0) + 1);
|
||||||
} catch (e) {
|
}
|
||||||
}
|
} catch (e) {
|
||||||
}
|
|
||||||
const N = files.length;
|
|
||||||
for (const [term, docCount] of df) {
|
|
||||||
this.idf.set(term, Math.log(N / docCount + 1));
|
|
||||||
}
|
|
||||||
for (const [path, tf] of tfs) {
|
|
||||||
const vec = /* @__PURE__ */ new Map();
|
|
||||||
let norm = 0;
|
|
||||||
for (const [term, tfVal] of tf) {
|
|
||||||
const idfVal = (_c = this.idf.get(term)) != null ? _c : 0;
|
|
||||||
const tfidf = tfVal * idfVal;
|
|
||||||
vec.set(term, tfidf);
|
|
||||||
norm += tfidf * tfidf;
|
|
||||||
}
|
|
||||||
norm = Math.sqrt(norm);
|
|
||||||
if (norm > 0) {
|
|
||||||
for (const [term, val] of vec) {
|
|
||||||
vec.set(term, val / norm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.docVectors.set(path, vec);
|
const N = files.length;
|
||||||
|
for (const [term, docCount] of df) {
|
||||||
|
this.idf.set(term, Math.log(N / docCount + 1));
|
||||||
|
}
|
||||||
|
for (const [path, tf] of tfs) {
|
||||||
|
const vec = /* @__PURE__ */ new Map();
|
||||||
|
let norm = 0;
|
||||||
|
for (const [term, tfVal] of tf) {
|
||||||
|
const idfVal = (_c = this.idf.get(term)) != null ? _c : 0;
|
||||||
|
const tfidf = tfVal * idfVal;
|
||||||
|
vec.set(term, tfidf);
|
||||||
|
norm += tfidf * tfidf;
|
||||||
|
}
|
||||||
|
norm = Math.sqrt(norm);
|
||||||
|
if (norm > 0) {
|
||||||
|
for (const [term, val] of vec) {
|
||||||
|
vec.set(term, val / norm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.docVectors.set(path, vec);
|
||||||
|
}
|
||||||
|
this.indexed = true;
|
||||||
|
if (this.onProgress)
|
||||||
|
this.onProgress(total, total);
|
||||||
|
} finally {
|
||||||
|
this.indexing = false;
|
||||||
}
|
}
|
||||||
this.indexed = true;
|
|
||||||
this.indexing = false;
|
|
||||||
if (this.onProgress)
|
|
||||||
this.onProgress(total, total);
|
|
||||||
}
|
}
|
||||||
isIndexed() {
|
isIndexed() {
|
||||||
return this.indexed;
|
return this.indexed;
|
||||||
@@ -865,11 +868,11 @@ var MemexChatPlugin = class extends import_obsidian4.Plugin {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.addSettingTab(new MemexChatSettingsTab(this.app, this));
|
this.addSettingTab(new MemexChatSettingsTab(this.app, this));
|
||||||
setTimeout(() => {
|
this.app.workspace.onLayoutReady(() => {
|
||||||
if (!this.search.isIndexed()) {
|
if (!this.search.isIndexed()) {
|
||||||
this.search.buildIndex().catch(console.error);
|
this.search.buildIndex().catch(console.error);
|
||||||
}
|
}
|
||||||
}, 3e3);
|
});
|
||||||
console.log("[Memex Chat] Plugin geladen");
|
console.log("[Memex Chat] Plugin geladen");
|
||||||
}
|
}
|
||||||
onunload() {
|
onunload() {
|
||||||
|
|||||||
+62
-58
@@ -59,70 +59,74 @@ export class VaultSearch {
|
|||||||
this.idf.clear();
|
this.idf.clear();
|
||||||
this.docContents.clear();
|
this.docContents.clear();
|
||||||
|
|
||||||
const files = this.app.vault.getMarkdownFiles();
|
try {
|
||||||
const total = files.length;
|
const files = this.app.vault.getMarkdownFiles();
|
||||||
const df: Map<string, number> = new Map(); // term -> doc count
|
const total = files.length;
|
||||||
|
const df: Map<string, number> = new Map(); // term -> doc count
|
||||||
|
|
||||||
// Step 1: Read all files, compute TF
|
// Step 1: Read all files, compute TF
|
||||||
const tfs: Map<string, Map<string, number>> = new Map();
|
const tfs: Map<string, Map<string, number>> = new Map();
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const file = files[i];
|
const file = files[i];
|
||||||
if (this.onProgress && i % 100 === 0) this.onProgress(i, total);
|
if (this.onProgress && i % 100 === 0) this.onProgress(i, total);
|
||||||
try {
|
try {
|
||||||
const raw = await this.app.vault.cachedRead(file);
|
const raw = await this.app.vault.cachedRead(file);
|
||||||
const clean = this.cleanContent(raw);
|
const clean = this.cleanContent(raw);
|
||||||
this.docContents.set(file.path, clean);
|
this.docContents.set(file.path, clean);
|
||||||
|
|
||||||
const tokens = this.tokenize(clean + " " + file.basename);
|
const tokens = this.tokenize(clean + " " + file.basename);
|
||||||
const tf: Map<string, number> = new Map();
|
const tf: Map<string, number> = new Map();
|
||||||
for (const t of tokens) {
|
for (const t of tokens) {
|
||||||
tf.set(t, (tf.get(t) ?? 0) + 1);
|
tf.set(t, (tf.get(t) ?? 0) + 1);
|
||||||
}
|
}
|
||||||
// Normalize TF
|
// Normalize TF
|
||||||
const maxTf = Math.max(...tf.values(), 1);
|
const maxTf = Math.max(...tf.values(), 1);
|
||||||
const normalizedTf: Map<string, number> = new Map();
|
const normalizedTf: Map<string, number> = new Map();
|
||||||
for (const [t, count] of tf) {
|
for (const [t, count] of tf) {
|
||||||
normalizedTf.set(t, count / maxTf);
|
normalizedTf.set(t, count / maxTf);
|
||||||
}
|
}
|
||||||
tfs.set(file.path, normalizedTf);
|
tfs.set(file.path, normalizedTf);
|
||||||
|
|
||||||
// Update DF
|
// Update DF
|
||||||
for (const t of tf.keys()) {
|
for (const t of tf.keys()) {
|
||||||
df.set(t, (df.get(t) ?? 0) + 1);
|
df.set(t, (df.get(t) ?? 0) + 1);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// skip unreadable files
|
// skip unreadable files
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2: Compute IDF and TF-IDF vectors
|
|
||||||
const N = files.length;
|
|
||||||
for (const [term, docCount] of df) {
|
|
||||||
this.idf.set(term, Math.log(N / docCount + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [path, tf] of tfs) {
|
|
||||||
const vec: Map<string, number> = new Map();
|
|
||||||
let norm = 0;
|
|
||||||
for (const [term, tfVal] of tf) {
|
|
||||||
const idfVal = this.idf.get(term) ?? 0;
|
|
||||||
const tfidf = tfVal * idfVal;
|
|
||||||
vec.set(term, tfidf);
|
|
||||||
norm += tfidf * tfidf;
|
|
||||||
}
|
|
||||||
// L2 normalize
|
|
||||||
norm = Math.sqrt(norm);
|
|
||||||
if (norm > 0) {
|
|
||||||
for (const [term, val] of vec) {
|
|
||||||
vec.set(term, val / norm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.docVectors.set(path, vec);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.indexed = true;
|
// Step 2: Compute IDF and TF-IDF vectors
|
||||||
this.indexing = false;
|
const N = files.length;
|
||||||
if (this.onProgress) this.onProgress(total, total);
|
for (const [term, docCount] of df) {
|
||||||
|
this.idf.set(term, Math.log(N / docCount + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [path, tf] of tfs) {
|
||||||
|
const vec: Map<string, number> = new Map();
|
||||||
|
let norm = 0;
|
||||||
|
for (const [term, tfVal] of tf) {
|
||||||
|
const idfVal = this.idf.get(term) ?? 0;
|
||||||
|
const tfidf = tfVal * idfVal;
|
||||||
|
vec.set(term, tfidf);
|
||||||
|
norm += tfidf * tfidf;
|
||||||
|
}
|
||||||
|
// L2 normalize
|
||||||
|
norm = Math.sqrt(norm);
|
||||||
|
if (norm > 0) {
|
||||||
|
for (const [term, val] of vec) {
|
||||||
|
vec.set(term, val / norm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.docVectors.set(path, vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.indexed = true;
|
||||||
|
if (this.onProgress) this.onProgress(total, total);
|
||||||
|
} finally {
|
||||||
|
// Always reset indexing so retries are possible if an error occurred
|
||||||
|
this.indexing = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isIndexed(): boolean {
|
isIndexed(): boolean {
|
||||||
|
|||||||
+3
-3
@@ -73,12 +73,12 @@ export default class MemexChatPlugin extends Plugin {
|
|||||||
// Settings tab
|
// Settings tab
|
||||||
this.addSettingTab(new MemexChatSettingsTab(this.app, this));
|
this.addSettingTab(new MemexChatSettingsTab(this.app, this));
|
||||||
|
|
||||||
// Build index in background after startup
|
// Build index once the workspace layout (and vault cache) is fully ready
|
||||||
setTimeout(() => {
|
this.app.workspace.onLayoutReady(() => {
|
||||||
if (!this.search.isIndexed()) {
|
if (!this.search.isIndexed()) {
|
||||||
this.search.buildIndex().catch(console.error);
|
this.search.buildIndex().catch(console.error);
|
||||||
}
|
}
|
||||||
}, 3000);
|
});
|
||||||
|
|
||||||
console.log("[Memex Chat] Plugin geladen");
|
console.log("[Memex Chat] Plugin geladen");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user