UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

284 lines (281 loc) 8.58 kB
import { Pinecone } from '@pinecone-database/pinecone'; import OpenAI from 'openai'; import { CacheService } from './cache-service.js'; import { ErrorHandler } from './error-handler.js'; // @ts-nocheck - Optional Pinecone and OpenAI dependencies class SemanticSearchService { constructor(config) { this.config = config; this.pinecone = new Pinecone({ apiKey: config.pinecone.apiKey }); this.openai = new OpenAI({ apiKey: config.openai.apiKey }); this.cache = new CacheService(config.redis); this.errorHandler = new ErrorHandler(); } async initialize() { try { await this.cache.connect(); const indexes = await this.pinecone.listIndexes(); const indexExists = indexes.indexes?.some(idx => idx.name === this.config.pinecone.indexName); if (!indexExists) { await this.createIndex(); } this.index = this.pinecone.index(this.config.pinecone.indexName); } catch (error) { this.errorHandler.handleError(error, { service: 'SemanticSearch', operation: 'initialize' }); throw error; } } async createIndex() { await this.pinecone.createIndex({ name: this.config.pinecone.indexName, dimension: 1536, metric: 'cosine', spec: { serverless: { cloud: 'aws', region: 'us-east-1' } } }); await this.waitForIndexReady(); } async waitForIndexReady(maxAttempts = 30) { for (let i = 0; i < maxAttempts; i++) { const indexes = await this.pinecone.listIndexes(); const index = indexes.indexes?.find(idx => idx.name === this.config.pinecone.indexName); if (index?.status?.ready) { return; } await new Promise(resolve => setTimeout(resolve, 1000)); } throw new Error('Index creation timeout'); } async indexDocuments(documents) { if (!this.index) { await this.initialize(); } try { const batchSize = 100; const batches = this.chunkArray(documents, batchSize); for (const batch of batches) { const vectors = await Promise.all(batch.map(async doc => { const embedding = await this.generateEmbedding(doc.content); return { id: doc.id, values: embedding, metadata: { content: doc.content.substring(0, 1000), title: doc.title || '', ...doc.metadata, tags: doc.tags?.join(',') || '' } }; })); await this.index.upsert(vectors); } await this.cache.delete('search:*'); } catch (error) { this.errorHandler.handleError(error, { service: 'SemanticSearch', operation: 'indexDocuments', metadata: { documentCount: documents.length } }); throw error; } } async search(query, options = {}) { if (!this.index) { await this.initialize(); } const { topK = 10, filter, includeMetadata = true, namespace } = options; const cacheKey = `search:${query}:${JSON.stringify(options)}`; if (this.config.costOptimization.enableCaching) { const cached = await this.cache.get(cacheKey); if (cached) return cached; } try { const queryEmbedding = await this.generateEmbedding(query); const queryResponse = await this.index.namespace(namespace || '').query({ vector: queryEmbedding, topK, includeMetadata, filter }); const results = queryResponse.matches.map(match => ({ id: match.id, content: match.metadata?.content || '', metadata: match.metadata || {}, score: match.score || 0, highlights: this.generateHighlights(query, match.metadata?.content || '') })); if (this.config.costOptimization.enableCaching) { await this.cache.set(cacheKey, results, 300); } return results; } catch (error) { return this.errorHandler.handleWithFallback(error, () => this.fallbackSearch(query, options), { service: 'SemanticSearch', operation: 'search', metadata: { query } }); } } async hybridSearch(query, options = {}) { const { semanticWeight = 0.7, keywordWeight = 0.3, topK = 10, filter } = options; const [semanticResults, keywordResults] = await Promise.all([this.search(query, { topK: topK * 2, filter }), this.keywordSearch(query, { topK: topK * 2, filter })]); const scoreMap = new Map(); const resultMap = new Map(); semanticResults.forEach(result => { const score = result.score * semanticWeight; scoreMap.set(result.id, score); resultMap.set(result.id, result); }); keywordResults.forEach(result => { const existingScore = scoreMap.get(result.id) || 0; const newScore = existingScore + result.score * keywordWeight; scoreMap.set(result.id, newScore); if (!resultMap.has(result.id)) { resultMap.set(result.id, result); } }); const combinedResults = Array.from(resultMap.values()).map(result => ({ ...result, score: scoreMap.get(result.id) || 0 })).sort((a, b) => b.score - a.score).slice(0, topK); return combinedResults; } async keywordSearch(query, options = {}) { const keywords = query.toLowerCase().split(/\s+/).filter(k => k.length > 2); if (!this.index) { return []; } try { const results = await this.index.namespace('').query({ vector: new Array(1536).fill(0), topK: options.topK || 10, includeMetadata: true, filter: { ...options.filter, $or: keywords.map(keyword => ({ content: { $contains: keyword } })) } }); return results.matches.map(match => { const content = match.metadata?.content || ''; const keywordScore = this.calculateKeywordScore(keywords, content); return { id: match.id, content, metadata: match.metadata || {}, score: keywordScore, highlights: this.generateHighlights(query, content) }; }); } catch (error) { console.error('Keyword search error:', error); return []; } } calculateKeywordScore(keywords, content) { const lowerContent = content.toLowerCase(); let score = 0; keywords.forEach(keyword => { const occurrences = (lowerContent.match(new RegExp(keyword, 'g')) || []).length; score += occurrences * (1 / keywords.length); }); return Math.min(score / 10, 1); } async generateEmbedding(text) { try { const response = await this.openai.embeddings.create({ model: 'text-embedding-ada-002', input: text.substring(0, 8000) }); return response.data[0].embedding; } catch (error) { this.errorHandler.handleError(error, { service: 'SemanticSearch', operation: 'generateEmbedding' }); throw error; } } generateHighlights(query, content) { const words = query.toLowerCase().split(/\s+/); const sentences = content.split(/[.!?]+/); const highlights = []; sentences.forEach(sentence => { const lowerSentence = sentence.toLowerCase(); const hasMatch = words.some(word => lowerSentence.includes(word)); if (hasMatch && sentence.trim().length > 20) { highlights.push(sentence.trim()); } }); return highlights.slice(0, 3); } fallbackSearch(query, options) { console.warn('Falling back to basic search implementation'); return [{ id: 'fallback-1', content: 'Search service is temporarily unavailable. Please try again later.', metadata: { fallback: true }, score: 0.5, highlights: [] }]; } chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } async deleteDocument(id) { if (!this.index) { await this.initialize(); } await this.index.deleteOne(id); await this.cache.delete('search:*'); } async deleteAllDocuments(namespace) { if (!this.index) { await this.initialize(); } await this.index.namespace(namespace || '').deleteAll(); await this.cache.delete('search:*'); } } export { SemanticSearchService }; //# sourceMappingURL=semantic-search-service.js.map