obsidian-qdrant/src/indexing/fileWatcher.ts
Nicholai 38889c1d65 Initial implementation of Qdrant Semantic Search plugin
- 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.
2025-10-23 08:48:29 -06:00

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;
}
}