@endlessblink/like-i-said-v2
Version:
Task Management & Memory for Claude - Track tasks, remember context, and maintain continuity across sessions with 27 powerful tools. Works with Claude Desktop and Claude Code.
158 lines (137 loc) • 4.9 kB
JavaScript
/**
* Universal Embeddings Provider
* Provides embeddings through multiple backends without native dependencies
*/
import { OllamaEmbeddings } from './ollama-embeddings.js';
import { optionalImport } from './optional-import.js';
import { settingsManager } from './settings-manager.js';
export class UniversalEmbeddings {
constructor() {
this.provider = null;
this.providerName = 'none';
this.initialized = false;
}
async initialize() {
if (this.initialized) return this.provider !== null;
try {
const semanticSearchEnabled = settingsManager.getSetting('features.enableSemanticSearch');
if (!semanticSearchEnabled) {
console.error('[UniversalEmbeddings] Semantic search disabled in settings');
return false;
}
const preferredProvider = settingsManager.getSetting('features.semanticSearchProvider');
// Try providers in order of preference
const providers = this.getProviderOrder(preferredProvider);
for (const providerName of providers) {
const success = await this.tryProvider(providerName);
if (success) {
this.providerName = providerName;
this.initialized = true;
console.error(`[UniversalEmbeddings] Using ${providerName} for embeddings`);
return true;
}
}
console.error('[UniversalEmbeddings] No embedding providers available');
this.initialized = true;
return false;
} catch (error) {
console.error('[UniversalEmbeddings] Initialization error:', error);
this.initialized = true;
return false;
}
}
getProviderOrder(preferred) {
const providers = ['ollama', 'xenova', 'none'];
if (preferred && providers.includes(preferred)) {
// Put preferred provider first
return [preferred, ...providers.filter(p => p !== preferred)];
}
return providers;
}
async tryProvider(name) {
try {
switch (name) {
case 'ollama':
const ollama = new OllamaEmbeddings();
const ollamaReady = await ollama.initialize();
if (ollamaReady) {
this.provider = ollama;
return true;
}
break;
case 'xenova':
// Only try xenova if not on Windows or if explicitly enabled
const blockOnWindows = settingsManager.getSetting('features.blockXenovaOnWindows');
if (process.platform === 'win32' && blockOnWindows) {
console.error('[UniversalEmbeddings] Skipping xenova on Windows');
return false;
}
const transformers = await optionalImport('@xenova/transformers', {
fallback: null,
onError: (error) => {
console.error('[UniversalEmbeddings] Failed to load @xenova/transformers:', error.message);
}
});
if (transformers) {
// Create a minimal xenova wrapper
this.provider = {
available: true,
isAvailable: () => true,
embed: async (text) => {
// Use transformers to create embeddings
const { pipeline } = transformers;
const extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
const output = await extractor(text, { pooling: 'mean', normalize: true });
return Array.from(output.data);
},
embedBatch: async (texts) => {
const embeddings = [];
for (const text of texts) {
const embedding = await this.provider.embed(text);
embeddings.push(embedding);
}
return embeddings;
}
};
return true;
}
break;
}
} catch (error) {
console.error(`[UniversalEmbeddings] Failed to initialize ${name}:`, error.message);
}
return false;
}
async embed(text) {
if (!this.provider) {
throw new Error('No embedding provider available');
}
return this.provider.embed(text);
}
async embedBatch(texts) {
if (!this.provider) {
throw new Error('No embedding provider available');
}
return this.provider.embedBatch(texts);
}
isAvailable() {
return this.provider !== null && this.provider.isAvailable();
}
getProviderName() {
return this.providerName;
}
// Compute cosine similarity between two embeddings
cosineSimilarity(a, b) {
if (!a || !b || a.length !== b.length) return 0;
let dotProduct = 0;
let normA = 0;
let normB = 0;
for (let i = 0; i < a.length; i++) {
dotProduct += a[i] * b[i];
normA += a[i] * a[i];
normB += b[i] * b[i];
}
if (normA === 0 || normB === 0) return 0;
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}
}