- 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.
197 lines
5.1 KiB
TypeScript
197 lines
5.1 KiB
TypeScript
import { App, Notice, Plugin, TFile } from 'obsidian';
|
|
import { PluginSettings, IndexingProgress } from './src/types';
|
|
import { DEFAULT_SETTINGS, validateSettings } from './src/settings';
|
|
import { IndexingOrchestrator } from './src/indexing/orchestrator';
|
|
import { SearchModal } from './src/search/searchModal';
|
|
import { QdrantSettingsTab } from './src/ui/settingsTab';
|
|
|
|
export default class QdrantPlugin extends Plugin {
|
|
settings: PluginSettings;
|
|
private indexingOrchestrator: IndexingOrchestrator | null = null;
|
|
private statusBarItem: HTMLElement | null = null;
|
|
|
|
async onload() {
|
|
await this.loadSettings();
|
|
|
|
// Validate settings
|
|
const errors = validateSettings(this.settings);
|
|
if (errors.length > 0) {
|
|
new Notice('Settings validation failed: ' + errors.join(', '));
|
|
}
|
|
|
|
// Initialize indexing orchestrator
|
|
try {
|
|
this.indexingOrchestrator = new IndexingOrchestrator(this.app, this.settings);
|
|
await this.indexingOrchestrator.initialize();
|
|
} catch (error) {
|
|
console.error('Failed to initialize indexing orchestrator:', error);
|
|
new Notice('Failed to initialize indexing system: ' + error.message);
|
|
}
|
|
|
|
// Add status bar item
|
|
this.setupStatusBar();
|
|
|
|
// Add commands
|
|
this.addCommands();
|
|
|
|
// Add settings tab
|
|
this.addSettingTab(new QdrantSettingsTab(this.app, this));
|
|
|
|
// Set up progress tracking
|
|
this.setupProgressTracking();
|
|
|
|
console.log('Qdrant Semantic Search plugin loaded');
|
|
}
|
|
|
|
onunload() {
|
|
// Shutdown indexing orchestrator
|
|
if (this.indexingOrchestrator) {
|
|
this.indexingOrchestrator.shutdown();
|
|
}
|
|
}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
|
|
private setupStatusBar() {
|
|
this.statusBarItem = this.addStatusBarItem();
|
|
this.updateStatusBar('Ready');
|
|
}
|
|
|
|
private addCommands() {
|
|
// Semantic search command
|
|
this.addCommand({
|
|
id: 'semantic-search',
|
|
name: 'Semantic search',
|
|
callback: () => {
|
|
if (!this.indexingOrchestrator?.isReady()) {
|
|
new Notice('Indexing system not ready. Please check your settings.');
|
|
return;
|
|
}
|
|
new SearchModal(this.app, this.settings).open();
|
|
}
|
|
});
|
|
|
|
// Index current file command
|
|
this.addCommand({
|
|
id: 'index-current-file',
|
|
name: 'Index current file',
|
|
checkCallback: (checking: boolean) => {
|
|
const activeFile = this.app.workspace.getActiveFile();
|
|
if (activeFile instanceof TFile) {
|
|
if (!checking) {
|
|
this.indexFile(activeFile);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
// Full reindex command
|
|
this.addCommand({
|
|
id: 'full-reindex',
|
|
name: 'Full reindex vault',
|
|
callback: () => {
|
|
this.indexFullVault();
|
|
}
|
|
});
|
|
|
|
// Clear index command
|
|
this.addCommand({
|
|
id: 'clear-index',
|
|
name: 'Clear index',
|
|
callback: () => {
|
|
this.clearIndex();
|
|
}
|
|
});
|
|
|
|
// Open graph view command
|
|
if (this.settings.enableGraphView) {
|
|
this.addCommand({
|
|
id: 'open-graph-view',
|
|
name: 'Open graph view',
|
|
callback: () => {
|
|
// TODO: Implement graph view
|
|
new Notice('Graph view not yet implemented');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private setupProgressTracking() {
|
|
if (!this.indexingOrchestrator) return;
|
|
|
|
this.indexingOrchestrator.setProgressCallback((progress: IndexingProgress) => {
|
|
this.updateStatusBar(progress);
|
|
});
|
|
|
|
this.indexingOrchestrator.setErrorCallback((error: string) => {
|
|
new Notice('Indexing error: ' + error);
|
|
});
|
|
}
|
|
|
|
private updateStatusBar(progress: IndexingProgress | string) {
|
|
if (!this.statusBarItem) return;
|
|
|
|
if (typeof progress === 'string') {
|
|
this.statusBarItem.setText(`Qdrant: ${progress}`);
|
|
return;
|
|
}
|
|
|
|
if (progress.isRunning) {
|
|
const percentage = progress.totalFiles > 0
|
|
? Math.round((progress.processedFiles / progress.totalFiles) * 100)
|
|
: 0;
|
|
this.statusBarItem.setText(`Qdrant: Indexing ${percentage}% (${progress.processedFiles}/${progress.totalFiles})`);
|
|
} else {
|
|
this.statusBarItem.setText('Qdrant: Ready');
|
|
}
|
|
}
|
|
|
|
// Public methods for settings tab
|
|
async testQdrantConnection(): Promise<boolean> {
|
|
if (!this.indexingOrchestrator) return false;
|
|
const connections = await this.indexingOrchestrator.testConnections();
|
|
return connections.qdrant;
|
|
}
|
|
|
|
async testOllamaConnection(): Promise<boolean> {
|
|
if (!this.indexingOrchestrator) return false;
|
|
const connections = await this.indexingOrchestrator.testConnections();
|
|
return connections.embedding;
|
|
}
|
|
|
|
async indexFullVault(): Promise<void> {
|
|
if (!this.indexingOrchestrator?.isReady()) {
|
|
throw new Error('Indexing system not ready');
|
|
}
|
|
await this.indexingOrchestrator.indexFullVault();
|
|
}
|
|
|
|
async indexFile(file: TFile): Promise<void> {
|
|
if (!this.indexingOrchestrator?.isReady()) {
|
|
throw new Error('Indexing system not ready');
|
|
}
|
|
await this.indexingOrchestrator.indexFile(file);
|
|
}
|
|
|
|
async clearIndex(): Promise<void> {
|
|
if (!this.indexingOrchestrator?.isReady()) {
|
|
throw new Error('Indexing system not ready');
|
|
}
|
|
await this.indexingOrchestrator.clearIndex();
|
|
}
|
|
|
|
async getIndexStats(): Promise<any> {
|
|
if (!this.indexingOrchestrator?.isReady()) {
|
|
throw new Error('Indexing system not ready');
|
|
}
|
|
return await this.indexingOrchestrator.getIndexStats();
|
|
}
|
|
} |