UNPKG

pulse-ai-utils

Version:

Utility functions and helpers for AI-powered applications

205 lines 7.56 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.searchContent = searchContent; exports.searchContentChunks = searchContentChunks; exports.hybridSearch = hybridSearch; exports.findSimilarContent = findSimilarContent; exports.updateContentMetadata = updateContentMetadata; const index_1 = require("./index"); const openai_helper_1 = __importDefault(require("../helpers/openai-helper")); /** * Search content using vector similarity */ async function searchContent(options) { const { query, embedding, limit = 10, threshold = 0.7, filters = {} } = options; try { // Use provided embedding or generate new one let searchEmbedding = embedding; if (!searchEmbedding) { const openai = new openai_helper_1.default(); searchEmbedding = await openai.generateEmbedding(query); } // Call the Supabase function with filters - use JSON version const { data, error } = await index_1.supabase.rpc('search_content_vectors_json', { query_embedding: searchEmbedding, match_threshold: threshold, match_count: limit, filter_type: filters.type || null, filter_source: filters.source || null, filter_date_from: filters.dateFrom || null, filter_date_to: filters.dateTo || null }); if (error) { console.error('Error searching content:', error); throw error; } // Apply additional filters that aren't in the SQL function let results = data; if (filters.seen !== undefined) { results = results.filter(r => r.seen === filters.seen); } return results; } catch (error) { console.error('Error in searchContent:', error); throw error; } } /** * Search content chunks for longer documents */ async function searchContentChunks(query, limit = 20, threshold = 0.7) { try { const openai = new openai_helper_1.default(); const searchEmbedding = await openai.generateEmbedding(query); const { data, error } = await index_1.supabase.rpc('search_content_chunks', { query_embedding: searchEmbedding, match_threshold: threshold, match_count: limit }); if (error) { console.error('Error searching content chunks:', error); throw error; } return data; } catch (error) { console.error('Error in searchContentChunks:', error); throw error; } } /** * Hybrid search combining vector and full-text search */ async function hybridSearch(query, options = {}) { const { vectorWeight = 0.7, textWeight = 0.3, limit = 10, filters = {} } = options; try { // Perform vector search const vectorResults = await searchContent({ query, limit: limit * 2, // Get more results for merging filters }); // Perform full-text search // Process query for PostgreSQL tsquery format (spaces → &) const processedQuery = query.trim().split(/\s+/).join(' & '); let textQuery = index_1.supabase .from('content') .select('*, similarity:search_text') .textSearch('search_text', processedQuery) .limit(limit * 2); // Apply filters if (filters.type) textQuery = textQuery.eq('type', filters.type); if (filters.source) textQuery = textQuery.eq('source', filters.source); if (filters.dateFrom) textQuery = textQuery.gte('content_date', filters.dateFrom); if (filters.dateTo) textQuery = textQuery.lte('content_date', filters.dateTo); if (filters.seen !== undefined) textQuery = textQuery.eq('seen', filters.seen); const { data: textResults, error } = await textQuery; if (error) { console.error('Error in text search:', error); throw error; } // Merge and score results const scoreMap = new Map(); const resultMap = new Map(); // Add vector search results vectorResults.forEach(result => { const score = result.similarity * vectorWeight; scoreMap.set(result.id, score); resultMap.set(result.id, result); }); // Add text search results textResults?.forEach((result) => { const currentScore = scoreMap.get(result.id) || 0; // Normalize text search relevance (assuming it's a count or rank) const textScore = (1 / (result.similarity + 1)) * textWeight; scoreMap.set(result.id, currentScore + textScore); if (!resultMap.has(result.id)) { resultMap.set(result.id, { ...result, similarity: textScore / textWeight // Normalize back }); } }); // Sort by combined score and return top results const sortedResults = Array.from(resultMap.entries()) .map(([id, result]) => ({ ...result, similarity: scoreMap.get(id) || 0 })) .sort((a, b) => b.similarity - a.similarity) .slice(0, limit); return sortedResults; } catch (error) { console.error('Error in hybridSearch:', error); throw error; } } /** * Get similar content based on an existing content ID */ async function findSimilarContent(contentId, limit = 5) { try { // First, get the content and its embedding const { data: content, error: fetchError } = await index_1.supabase .from('content') .select('*') .eq('id', contentId) .single(); if (fetchError || !content) { throw new Error(`Content ${contentId} not found`); } if (!content.embedding) { throw new Error(`Content ${contentId} has no embedding`); } // Search for similar content, excluding the original const { data, error } = await index_1.supabase.rpc('search_content_vectors_json', { query_embedding: content.embedding, match_threshold: 0.5, match_count: limit + 1, // Get one extra to exclude self filter_type: content.type // Only search within same type }); if (error) { console.error('Error finding similar content:', error); throw error; } // Filter out the original content const results = data .filter(r => r.id !== contentId) .slice(0, limit); return results; } catch (error) { console.error('Error in findSimilarContent:', error); throw error; } } /** * Update content metadata (seen, rank) after user interaction */ async function updateContentMetadata(contentId, updates) { try { const { error } = await index_1.supabase .from('content') .update(updates) .eq('id', contentId); if (error) { console.error('Error updating content metadata:', error); throw error; } } catch (error) { console.error('Error in updateContentMetadata:', error); throw error; } } //# sourceMappingURL=vector-search.js.map