mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-05-18 06:03:49 +00:00
115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
// Utility functions for calculating semantic similarity between documents and memories
|
|
|
|
/**
|
|
* Calculate cosine similarity between two normalized vectors (unit vectors)
|
|
* Since all embeddings in this system are normalized using normalizeEmbeddingFast,
|
|
* cosine similarity equals dot product for unit vectors.
|
|
*/
|
|
export const cosineSimilarity = (
|
|
vectorA: number[],
|
|
vectorB: number[],
|
|
): number => {
|
|
if (vectorA.length !== vectorB.length) {
|
|
throw new Error("Vectors must have the same length")
|
|
}
|
|
|
|
let dotProduct = 0
|
|
|
|
for (let i = 0; i < vectorA.length; i++) {
|
|
const vectorAi = vectorA[i]
|
|
const vectorBi = vectorB[i]
|
|
if (
|
|
typeof vectorAi !== "number" ||
|
|
typeof vectorBi !== "number" ||
|
|
isNaN(vectorAi) ||
|
|
isNaN(vectorBi)
|
|
) {
|
|
throw new Error("Vectors must contain only numbers")
|
|
}
|
|
dotProduct += vectorAi * vectorBi
|
|
}
|
|
|
|
return dotProduct
|
|
}
|
|
|
|
/**
|
|
* Calculate semantic similarity between two documents
|
|
* Returns a value between 0 and 1, where 1 is most similar
|
|
*/
|
|
export const calculateSemanticSimilarity = (
|
|
document1Embedding: number[] | null,
|
|
document2Embedding: number[] | null,
|
|
): number => {
|
|
// If we have both embeddings, use cosine similarity
|
|
if (
|
|
document1Embedding &&
|
|
document2Embedding &&
|
|
document1Embedding.length > 0 &&
|
|
document2Embedding.length > 0
|
|
) {
|
|
const similarity = cosineSimilarity(document1Embedding, document2Embedding)
|
|
// Convert from [-1, 1] to [0, 1] range
|
|
return similarity >= 0 ? similarity : 0
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
/**
|
|
* Calculate semantic similarity between a document and memory entry
|
|
* Returns a value between 0 and 1, where 1 is most similar
|
|
*/
|
|
export const calculateDocumentMemorySimilarity = (
|
|
documentEmbedding: number[] | null,
|
|
memoryEmbedding: number[] | null,
|
|
relevanceScore?: number | null,
|
|
): number => {
|
|
// If we have both embeddings, use cosine similarity
|
|
if (
|
|
documentEmbedding &&
|
|
memoryEmbedding &&
|
|
documentEmbedding.length > 0 &&
|
|
memoryEmbedding.length > 0
|
|
) {
|
|
const similarity = cosineSimilarity(documentEmbedding, memoryEmbedding)
|
|
// Convert from [-1, 1] to [0, 1] range
|
|
return similarity >= 0 ? similarity : 0
|
|
}
|
|
|
|
// Fall back to relevance score from database (0-100 scale)
|
|
if (relevanceScore !== null && relevanceScore !== undefined) {
|
|
return Math.max(0, Math.min(1, relevanceScore / 100))
|
|
}
|
|
|
|
// Default similarity for connections without embeddings or relevance scores
|
|
return 0.5
|
|
}
|
|
|
|
/**
|
|
* Get visual properties for connection based on similarity
|
|
*/
|
|
export const getConnectionVisualProps = (similarity: number) => {
|
|
// Ensure similarity is between 0 and 1
|
|
const normalizedSimilarity = Math.max(0, Math.min(1, similarity))
|
|
|
|
return {
|
|
opacity: Math.max(0, normalizedSimilarity), // 0 to 1 range
|
|
thickness: Math.max(1, normalizedSimilarity * 4), // 1 to 4 pixels
|
|
glow: normalizedSimilarity * 0.6, // Glow intensity
|
|
pulseDuration: 2000 + (1 - normalizedSimilarity) * 3000, // Faster pulse for higher similarity
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate magical color based on similarity and connection type
|
|
*/
|
|
export const getMagicalConnectionColor = (
|
|
similarity: number,
|
|
hue = 220,
|
|
): string => {
|
|
const normalizedSimilarity = Math.max(0, Math.min(1, similarity))
|
|
const saturation = 60 + normalizedSimilarity * 40 // 60% to 100%
|
|
const lightness = 40 + normalizedSimilarity * 30 // 40% to 70%
|
|
|
|
return `hsl(${hue}, ${saturation}%, ${lightness}%)`
|
|
}
|