mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* feat: introduce unified search system with legacy support - Added a new `SearchSystem` interface and `SearchSystemFactory` to manage both legacy and v3 search implementations. - Updated various components to utilize the new factory for creating retrievers and managing indexing operations. - Introduced `useLegacySearch` setting to toggle between legacy and v3 search systems, enhancing user control over search functionality. - Refactored existing search-related code to streamline indexing and retrieval processes, improving overall performance and maintainability. - Updated documentation to reflect changes in search architecture and settings management. * feat: enhance V3 indexing responses with document count - Updated the V3SearchIndexer to return the document count after incremental and full indexing operations. - Modified success messages to include the number of documents indexed, improving user feedback and clarity on indexing results. * feat: improve error handling and user feedback for indexing operations - Enhanced the indexing process by adding error handling for both incremental and full indexing operations, providing users with clear notifications in case of failures. - Updated the `refreshVaultIndex` and `forceReindexVault` functions to log errors and display messages when indexing fails, improving overall user experience and transparency. - Refactored the main indexing logic in `CopilotPlugin` to ensure proper loading of indexes based on the selected strategy, enhancing functionality and reliability. * fix: update indexing logic for search system consistency - Changed comments to clarify that the index loading occurs even when search is disabled. - Refactored the index loading process to consistently use `SearchSystemFactory`, enhancing code clarity and maintainability. - Updated the condition for enabling semantic search to account for the new `useLegacySearch` setting, improving search functionality control.
260 lines
7.7 KiB
TypeScript
260 lines
7.7 KiB
TypeScript
import { logInfo, logWarn } from "@/logger";
|
|
import { getSettings } from "@/settings/model";
|
|
import { BaseRetriever } from "@langchain/core/retrievers";
|
|
import { App, Notice } from "obsidian";
|
|
import { HybridRetriever } from "./hybridRetriever";
|
|
import { TieredLexicalRetriever } from "./v3/TieredLexicalRetriever";
|
|
|
|
/**
|
|
* Unified interface for search operations (retrieval and indexing)
|
|
*/
|
|
export interface IndexingResult {
|
|
success: boolean;
|
|
documentCount?: number;
|
|
message?: string;
|
|
}
|
|
|
|
/**
|
|
* Interface for search indexing operations
|
|
*/
|
|
export interface SearchIndexer {
|
|
indexVaultIncremental(app: App): Promise<IndexingResult>;
|
|
indexVaultFull(app: App): Promise<IndexingResult>;
|
|
ensureLoaded(app: App): Promise<void>;
|
|
clearIndex(app: App): Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Retriever options interface
|
|
*/
|
|
export interface RetrieverOptions {
|
|
minSimilarityScore?: number;
|
|
maxK: number;
|
|
salientTerms: string[];
|
|
timeRange?: { startTime: number; endTime: number };
|
|
textWeight?: number;
|
|
returnAll?: boolean;
|
|
useRerankerThreshold?: number;
|
|
}
|
|
|
|
/**
|
|
* Abstract interface for search systems
|
|
*/
|
|
export interface SearchSystem {
|
|
readonly name: string;
|
|
readonly version: string;
|
|
readonly isLegacy: boolean;
|
|
|
|
createRetriever(app: App, options: RetrieverOptions): BaseRetriever;
|
|
getIndexer(): SearchIndexer;
|
|
isSemanticSearchEnabled(): boolean;
|
|
}
|
|
|
|
/**
|
|
* Legacy Orama-based search indexer implementation
|
|
*/
|
|
class LegacySearchIndexer implements SearchIndexer {
|
|
async indexVaultIncremental(app: App): Promise<IndexingResult> {
|
|
logInfo("LegacySearchIndexer: Incremental indexing");
|
|
try {
|
|
const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
|
|
const count = await VectorStoreManager.getInstance().indexVaultToVectorStore(false);
|
|
return { success: true, documentCount: count };
|
|
} catch (error) {
|
|
return { success: false, message: error.message };
|
|
}
|
|
}
|
|
|
|
async indexVaultFull(app: App): Promise<IndexingResult> {
|
|
logInfo("LegacySearchIndexer: Full indexing");
|
|
try {
|
|
const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
|
|
const count = await VectorStoreManager.getInstance().indexVaultToVectorStore(true);
|
|
return { success: true, documentCount: count };
|
|
} catch (error) {
|
|
return { success: false, message: error.message };
|
|
}
|
|
}
|
|
|
|
async ensureLoaded(app: App): Promise<void> {
|
|
logInfo("LegacySearchIndexer: Ensuring index is loaded");
|
|
const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
|
|
const isEmpty = await VectorStoreManager.getInstance().isIndexEmpty();
|
|
if (isEmpty) {
|
|
new Notice("Legacy Orama index is empty. Please rebuild the index.");
|
|
}
|
|
}
|
|
|
|
async clearIndex(app: App): Promise<void> {
|
|
logInfo("LegacySearchIndexer: Clearing index");
|
|
const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
|
|
await VectorStoreManager.getInstance().clearIndex();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* V3 MemoryIndexManager-based search indexer implementation
|
|
*/
|
|
class V3SearchIndexer implements SearchIndexer {
|
|
async indexVaultIncremental(app: App): Promise<IndexingResult> {
|
|
logInfo("V3SearchIndexer: Incremental indexing");
|
|
try {
|
|
const { MemoryIndexManager } = await import("@/search/v3/MemoryIndexManager");
|
|
const documentCount = await MemoryIndexManager.getInstance(app).indexVaultIncremental();
|
|
await MemoryIndexManager.getInstance(app).ensureLoaded();
|
|
return {
|
|
success: true,
|
|
documentCount,
|
|
message: `V3 index updated with ${documentCount} documents`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, message: error.message };
|
|
}
|
|
}
|
|
|
|
async indexVaultFull(app: App): Promise<IndexingResult> {
|
|
logInfo("V3SearchIndexer: Full indexing");
|
|
try {
|
|
const { MemoryIndexManager } = await import("@/search/v3/MemoryIndexManager");
|
|
const documentCount = await MemoryIndexManager.getInstance(app).indexVault();
|
|
await MemoryIndexManager.getInstance(app).ensureLoaded();
|
|
return {
|
|
success: true,
|
|
documentCount,
|
|
message: `V3 index rebuilt with ${documentCount} documents`,
|
|
};
|
|
} catch (error) {
|
|
return { success: false, message: error.message };
|
|
}
|
|
}
|
|
|
|
async ensureLoaded(app: App): Promise<void> {
|
|
logInfo("V3SearchIndexer: Ensuring index is loaded");
|
|
const { MemoryIndexManager } = await import("@/search/v3/MemoryIndexManager");
|
|
await MemoryIndexManager.getInstance(app).ensureLoaded();
|
|
}
|
|
|
|
async clearIndex(app: App): Promise<void> {
|
|
logInfo("V3SearchIndexer: Clear not supported - index rebuilds on demand");
|
|
new Notice("V3 search rebuilds index automatically on demand.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Legacy Orama-based search system implementation
|
|
* @deprecated Will be removed in v3.0.0
|
|
*/
|
|
class LegacySearchSystem implements SearchSystem {
|
|
readonly name = "Orama";
|
|
readonly version = "legacy";
|
|
readonly isLegacy = true;
|
|
private indexer = new LegacySearchIndexer();
|
|
|
|
createRetriever(app: App, options: RetrieverOptions): BaseRetriever {
|
|
return new HybridRetriever({
|
|
...options,
|
|
minSimilarityScore: options.minSimilarityScore ?? 0.1,
|
|
});
|
|
}
|
|
|
|
getIndexer(): SearchIndexer {
|
|
return this.indexer;
|
|
}
|
|
|
|
isSemanticSearchEnabled(): boolean {
|
|
// Legacy always has semantic capability via embeddings
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* V3 TieredLexical search system implementation
|
|
*/
|
|
class V3SearchSystem implements SearchSystem {
|
|
readonly name = "TieredLexical";
|
|
readonly version = "v3";
|
|
readonly isLegacy = false;
|
|
private indexer = new V3SearchIndexer();
|
|
|
|
createRetriever(app: App, options: RetrieverOptions): BaseRetriever {
|
|
return new TieredLexicalRetriever(app, options);
|
|
}
|
|
|
|
getIndexer(): SearchIndexer {
|
|
return this.indexer;
|
|
}
|
|
|
|
isSemanticSearchEnabled(): boolean {
|
|
return getSettings().enableSemanticSearchV3;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Factory for creating and managing search systems
|
|
*/
|
|
export class SearchSystemFactory {
|
|
private static searchSystem: SearchSystem | null = null;
|
|
|
|
/**
|
|
* Get the current search system based on settings
|
|
*/
|
|
static getSearchSystem(): SearchSystem {
|
|
const settings = getSettings();
|
|
|
|
// Check if we need to recreate due to settings change
|
|
if (this.searchSystem) {
|
|
const isCurrentlyLegacy = this.searchSystem.isLegacy;
|
|
const shouldBeLegacy = settings.useLegacySearch;
|
|
|
|
if (isCurrentlyLegacy !== shouldBeLegacy) {
|
|
this.searchSystem = null;
|
|
}
|
|
}
|
|
|
|
if (!this.searchSystem) {
|
|
if (settings.useLegacySearch) {
|
|
logWarn(
|
|
"DEPRECATED: Legacy Orama search is deprecated and will be removed in a future version. Please switch to v3 search in QA Settings."
|
|
);
|
|
this.searchSystem = new LegacySearchSystem();
|
|
} else {
|
|
this.searchSystem = new V3SearchSystem();
|
|
}
|
|
}
|
|
|
|
return this.searchSystem;
|
|
}
|
|
|
|
/**
|
|
* Reset the cached search system (use when settings change)
|
|
*/
|
|
static reset(): void {
|
|
this.searchSystem = null;
|
|
}
|
|
|
|
/**
|
|
* Create a retriever using the current search system
|
|
*/
|
|
static createRetriever(app: App, options: RetrieverOptions): BaseRetriever {
|
|
const searchSystem = this.getSearchSystem();
|
|
logInfo(`Creating retriever using ${searchSystem.name} (${searchSystem.version})`);
|
|
|
|
try {
|
|
return searchSystem.createRetriever(app, options);
|
|
} catch (error) {
|
|
// Fallback to v3 if legacy fails
|
|
if (searchSystem.isLegacy) {
|
|
logWarn("Failed to create legacy retriever, falling back to v3");
|
|
return new V3SearchSystem().createRetriever(app, options);
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the indexer for the current search system
|
|
*/
|
|
static getIndexer(): SearchIndexer {
|
|
return this.getSearchSystem().getIndexer();
|
|
}
|
|
}
|