83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import {Memory} from './memory/memory.ts';
|
|
|
|
export function cosineDistance(a: number[], b: number[]): number {
|
|
let dot = 0, normA = 0, normB = 0;
|
|
for(let i = 0; i < a.length; i++) {
|
|
dot += a[i] * b[i];
|
|
normA += a[i] * a[i];
|
|
normB += b[i] * b[i];
|
|
}
|
|
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
return denom === 0 ? 1 : 1 - dot / denom;
|
|
}
|
|
|
|
export async function embedMemoryFields(node: Memory, llm: any): Promise<void> {
|
|
const body = stripHeader(node.content);
|
|
const [titleE] = await llm.embedding(node.name.split('/').pop() || node.name);
|
|
const [descE] = await llm.embedding(node.description || '');
|
|
const bodyChunks = body ? await llm.embedding(body) : [];
|
|
if(titleE) node.titleEmbedding = titleE.embedding;
|
|
if(descE) node.embedding = descE.embedding;
|
|
node.bodyEmbeddings = bodyChunks.map((c: any) => c.embedding).filter(Boolean);
|
|
}
|
|
|
|
export function euclideanDistance(a: number[], b: number[]): number {
|
|
let sum = 0;
|
|
for(let i = 0; i < a.length; i++) {
|
|
const d = a[i] - b[i];
|
|
sum += d * d;
|
|
}
|
|
return Math.sqrt(sum);
|
|
}
|
|
|
|
function getWeekStart(date: Date = new Date()): string {
|
|
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
const day = d.getUTCDay();
|
|
const diff = day === 0 ? -6 : 1 - day;
|
|
d.setUTCDate(d.getUTCDate() + diff);
|
|
return d.toISOString().slice(0, 10);
|
|
}
|
|
|
|
export function journalDescription(journalName?: string): string {
|
|
const start = journalName?.split('/').pop() || getWeekStart();
|
|
const d = new Date(`${start}T00:00:00Z`);
|
|
d.setUTCDate(d.getUTCDate() + 6);
|
|
const end = d.toISOString().slice(0, 10);
|
|
return `Log from ${start} - ${end}`;
|
|
}
|
|
|
|
function parseFrontmatter(content: string): {fm: Map<string, string>, body: string} {
|
|
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
if(!match) return {fm: new Map(), body: content};
|
|
const fm = new Map<string, string>();
|
|
for(const line of match[1].split('\n')) {
|
|
const i = line.indexOf(':');
|
|
if(i === -1) continue;
|
|
const key = line.slice(0, i).trim();
|
|
const raw = line.slice(i + 1).trim();
|
|
let value = raw;
|
|
try { value = JSON.parse(raw); } catch { }
|
|
fm.set(key, value);
|
|
}
|
|
return {fm, body: match[2]};
|
|
}
|
|
|
|
export function writeFrontmatter(fm: Map<string, string>, body: string): string {
|
|
const lines = [...fm.entries()].map(([k, v]) =>
|
|
`${k}: ${JSON.stringify(String(v).replace(/\s+/g, ' ').trim())}`);
|
|
return `---\n${lines.join('\n')}\n---\n\n${body.trimStart()}`;
|
|
}
|
|
|
|
export function stripHeader(content: string): string {
|
|
return content.replace(/^---[\s\S]*?\n---\n?/, '').trimStart();
|
|
}
|
|
|
|
export function touchHeader(node: Memory, body: string): string {
|
|
const {fm} = parseFrontmatter(node.content);
|
|
fm.set('name', node.name);
|
|
fm.set('description', (node.name.startsWith('Journal/') ? journalDescription(node.name) : node.description)
|
|
|| 'Persistent memory document');
|
|
fm.set('modified', new Date().toISOString());
|
|
return writeFrontmatter(fm, stripHeader(body));
|
|
}
|