- Complete plugin architecture with modular design - Qdrant client with HTTP integration using requestUrl - Ollama and OpenAI embedding providers with batching - Hybrid chunking (semantic + size-based fallback) - Content extractors for markdown, code, PDFs, and images - Real-time indexing with file watcher and queue - Search modal with keyboard navigation - Comprehensive settings UI with connection testing - Graph visualization framework (basic implementation) - Full TypeScript types and error handling - Desktop-only plugin with status bar integration - Complete documentation and setup guide Features implemented: ✅ Semantic search with vector embeddings ✅ Multiple embedding providers (Ollama/OpenAI) ✅ Rich content extraction (markdown, code, PDFs, images) ✅ Smart chunking with heading-based splits ✅ Real-time file indexing with progress tracking ✅ Standalone search interface ✅ Comprehensive settings and configuration ✅ Graph view foundation for document relationships ✅ Full error handling and logging ✅ Complete documentation and troubleshooting guide Ready for testing with Qdrant instance and embedding provider setup.
159 lines
3.7 KiB
TypeScript
159 lines
3.7 KiB
TypeScript
import { TFile, Vault } from 'obsidian';
|
|
import { IndexingQueue } from './indexQueue';
|
|
|
|
export class FileWatcher {
|
|
private vault: Vault;
|
|
private indexingQueue: IndexingQueue;
|
|
private debounceTimeout: number | null = null;
|
|
private debounceDelay = 300; // 300ms debounce
|
|
private pendingFiles: Set<string> = new Set();
|
|
|
|
constructor(vault: Vault, indexingQueue: IndexingQueue) {
|
|
this.vault = vault;
|
|
this.indexingQueue = indexingQueue;
|
|
}
|
|
|
|
/**
|
|
* Start watching for file changes
|
|
*/
|
|
startWatching(): void {
|
|
// Watch for file creation
|
|
this.vault.on('create', (file) => {
|
|
if (file instanceof TFile) {
|
|
this.handleFileChange(file, 'create');
|
|
}
|
|
});
|
|
|
|
// Watch for file modification
|
|
this.vault.on('modify', (file) => {
|
|
if (file instanceof TFile) {
|
|
this.handleFileChange(file, 'update');
|
|
}
|
|
});
|
|
|
|
// Watch for file deletion
|
|
this.vault.on('delete', (file) => {
|
|
if (file instanceof TFile) {
|
|
this.handleFileChange(file, 'delete');
|
|
}
|
|
});
|
|
|
|
// Watch for file renaming
|
|
this.vault.on('rename', (file, oldPath) => {
|
|
if (file instanceof TFile) {
|
|
// Handle the old file as deleted
|
|
this.handleFileChange(file, 'delete', oldPath);
|
|
// Handle the new file as created
|
|
this.handleFileChange(file, 'create');
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stop watching for file changes
|
|
*/
|
|
stopWatching(): void {
|
|
if (this.debounceTimeout) {
|
|
clearTimeout(this.debounceTimeout);
|
|
this.debounceTimeout = null;
|
|
}
|
|
this.pendingFiles.clear();
|
|
}
|
|
|
|
/**
|
|
* Handle file change with debouncing
|
|
*/
|
|
private handleFileChange(file: TFile, action: 'create' | 'update' | 'delete', oldPath?: string): void {
|
|
const filePath = oldPath || file.path;
|
|
|
|
// Add to pending files
|
|
this.pendingFiles.add(filePath);
|
|
|
|
// Clear existing timeout
|
|
if (this.debounceTimeout) {
|
|
clearTimeout(this.debounceTimeout);
|
|
}
|
|
|
|
// Set new timeout
|
|
this.debounceTimeout = window.setTimeout(() => {
|
|
this.processPendingFiles();
|
|
}, this.debounceDelay);
|
|
}
|
|
|
|
/**
|
|
* Process all pending file changes
|
|
*/
|
|
private processPendingFiles(): void {
|
|
if (this.pendingFiles.size === 0) {
|
|
return;
|
|
}
|
|
|
|
const filesToProcess: TFile[] = [];
|
|
|
|
for (const filePath of this.pendingFiles) {
|
|
const file = this.vault.getAbstractFileByPath(filePath);
|
|
if (file instanceof TFile) {
|
|
filesToProcess.push(file);
|
|
}
|
|
}
|
|
|
|
if (filesToProcess.length > 0) {
|
|
// Add files to indexing queue
|
|
this.indexingQueue.addFiles(filesToProcess, 'update');
|
|
|
|
// Start processing if not already running
|
|
this.indexingQueue.startProcessing();
|
|
}
|
|
|
|
// Clear pending files
|
|
this.pendingFiles.clear();
|
|
}
|
|
|
|
/**
|
|
* Manually trigger indexing for specific files
|
|
*/
|
|
triggerIndexing(files: TFile[], action: 'create' | 'update' | 'delete' = 'update'): void {
|
|
this.indexingQueue.addFiles(files, action);
|
|
this.indexingQueue.startProcessing();
|
|
}
|
|
|
|
/**
|
|
* Get pending files count
|
|
*/
|
|
getPendingFilesCount(): number {
|
|
return this.pendingFiles.size;
|
|
}
|
|
|
|
/**
|
|
* Get pending files list
|
|
*/
|
|
getPendingFiles(): string[] {
|
|
return Array.from(this.pendingFiles);
|
|
}
|
|
|
|
/**
|
|
* Clear pending files
|
|
*/
|
|
clearPendingFiles(): void {
|
|
this.pendingFiles.clear();
|
|
if (this.debounceTimeout) {
|
|
clearTimeout(this.debounceTimeout);
|
|
this.debounceTimeout = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set debounce delay
|
|
*/
|
|
setDebounceDelay(delay: number): void {
|
|
this.debounceDelay = delay;
|
|
}
|
|
|
|
/**
|
|
* Get current debounce delay
|
|
*/
|
|
getDebounceDelay(): number {
|
|
return this.debounceDelay;
|
|
}
|
|
}
|