From 0dc01a7965286726f885105c163ecd4c060f5351 Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:21:22 +0330 Subject: [PATCH] Import source: app/festival-submission/backend/src/lib/embeddings Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- .../backend/src/lib/embeddings.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 app/festival-submission/backend/src/lib/embeddings.ts diff --git a/app/festival-submission/backend/src/lib/embeddings.ts b/app/festival-submission/backend/src/lib/embeddings.ts new file mode 100644 index 0000000..7c5ab61 --- /dev/null +++ b/app/festival-submission/backend/src/lib/embeddings.ts @@ -0,0 +1,47 @@ +import { prisma } from '../db.js'; + +// Simulated embedding generation for Phase 14 demo +// In production, this would call OpenAI text-embedding-3-small or similar +export async function generateEmbedding(text: string): Promise { + // Deterministic-ish random for demo purposes based on text length + const vector = new Array(768).fill(0).map((_, i) => Math.sin(text.length * i)); + // Normalize vector + const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0)); + return vector.map(val => val / magnitude); +} + +export async function storeArticleEmbedding(articleId: string, text: string) { + const vector = await generateEmbedding(text); + + // Format for pgvector: '[0.1, 0.2, ...]' + const vectorString = `[${vector.join(',')}]`; + + await prisma.$executeRaw` + INSERT INTO "ArticleEmbedding" ("id", "articleId", "embedding", "updatedAt") + VALUES (gen_random_uuid(), ${articleId}, ${vectorString}::vector, NOW()) + ON CONFLICT ("articleId") DO UPDATE SET + "embedding" = EXCLUDED."embedding", + "updatedAt" = NOW(); + `; +} + +export async function semanticSearch(query: string, limit = 5) { + const queryVector = await generateEmbedding(query); + const vectorString = `[${queryVector.join(',')}]`; + + // Cosine distance operator <=> in pgvector + const results = await prisma.$queryRaw>` + SELECT + e."id", + e."articleId", + (e."embedding" <=> ${vectorString}::vector) as distance, + a."title", + a."slug" + FROM "ArticleEmbedding" e + JOIN "Article" a ON e."articleId" = a."id" + ORDER BY distance ASC + LIMIT ${limit}; + `; + + return results; +}