1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
// 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}%)`
}
|