obsidian-qdrant/main.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

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