Search
Code115
// JigsawStack + Orama Strategy: Semantic search using JigsawStack embeddings and Orama// JigsawStack embeddings function (v2 API)export const generateEmbeddings = async (content: string): Promise<number[] | null> => { const JIGSAWSTACK_API_KEY = Deno.env.get("JIGSAWSTACK_API_KEY"); if (!JIGSAWSTACK_API_KEY) { console.warn("JIGSAWSTACK_API_KEY not found - embeddings disabled"); return null; // JigsawStack v2 returns: { success: true, embeddings: [[...]], chunks: [...], _usage: {...} } // embeddings is an array of arrays, we want the first embedding vector if (data.success && data.embeddings && Array.isArray(data.embeddings) && data.embeddings.length > 0) { return data.embeddings[0]; // Return the first embedding vector } const errorMessage = error instanceof Error ? error.message : String(error); console.error("JigsawStack embeddings failed:", errorMessage); return null; name: "jigsawstack-orama", description: "Semantic search using JigsawStack embeddings with Orama vector search", search: async (query: string, pages: Page[], options: SearchOptions = {}): Promise<SearchResult[]> => { const queryEmbedStart = performance.now(); const queryEmbedding = await generateEmbeddings(query); if (enableTiming) timings.queryEmbedding = performance.now() - queryEmbedStart; // Determine embedding dimension from query (or first page with embeddings) const embeddingDimension = queryEmbedding.length; // Prepare documents with embeddings for insertion const documents: Array<{ // Collect pages with embeddings const docPrepStart = performance.now(); for (const page of pages) { // Use cached embeddings if available, otherwise generate let pageEmbedding = page.embeddings; if (!pageEmbedding) { // Generate embedding if not cached (should be cached from recalculate, but handle missing) pageEmbedding = await generateEmbeddings(page.content); if (!pageEmbedding || pageEmbedding.length !== embeddingDimension) { continue; // Skip pages without embeddings or wrong dimension }// OpenAI + Orama Strategy: Semantic search using OpenAI embeddings and Orama// Faster than JigsawStack (~100-200ms vs ~550ms for query embeddings)// OpenAI embeddings functionexport const generateEmbeddings = async (content: string): Promise<number[] | null> => { const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY"); const OPENAI_API_URL = "https://api.openai.com/v1/embeddings"; if (!OPENAI_API_KEY) { console.warn("OPENAI_API_KEY not found - embeddings disabled"); return null; const errorMessage = error instanceof Error ? error.message : String(error); console.error("OpenAI embeddings failed:", errorMessage); return null;// No caching for query embeddings - we want to benchmark actual API performance name: "openai-orama", description: "Semantic search using OpenAI embeddings with Orama vector search (faster than JigsawStack)", search: async (query: string, pages: Page[], options: SearchOptions = {}): Promise<SearchResult[]> => { const queryEmbedStart = performance.now(); const queryEmbedding = await generateEmbeddings(query); if (enableTiming) { // Determine embedding dimension from query (or first page with embeddings) const embeddingDimension = queryEmbedding.length; // Prepare documents with embeddings for insertion const documents: Array<{ // Collect pages with embeddings const docPrepStart = performance.now(); for (const page of pages) { // Use cached embeddings if available, otherwise generate let pageEmbedding = page.embeddings; if (!pageEmbedding) { // Generate embedding if not cached (should be cached from recalculate, but handle missing) pageEmbedding = await generateEmbeddings(page.content); if (!pageEmbedding || pageEmbedding.length !== embeddingDimension) { continue; // Skip pages without embeddings or wrong dimension }// OpenAI embeddings functionexport const generateEmbeddings = async (content: string): Promise<number[] | null> => { const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY"); const OPENAI_API_URL = "https://api.openai.com/v1/embeddings"; if (!OPENAI_API_KEY) { console.warn("OPENAI_API_KEY not found - embeddings disabled"); return null; const errorMessage = error instanceof Error ? error.message : String(error); console.error("OpenAI embeddings failed:", errorMessage); return null;// No caching for query embeddings - we want to benchmark actual API performance name: "openai-cosine", description: "Semantic search using OpenAI embeddings with direct cosine similarity (fastest for small datasets)", search: async (query: string, pages: Page[], options: SearchOptions = {}): Promise<SearchResult[]> => { const queryEmbedStart = performance.now(); const queryEmbedding = await generateEmbeddings(query); if (enableTiming) { for (const page of pages) { const pageEmbedding = page.embeddings; if (!pageEmbedding || pageEmbedding.length !== queryEmbedding.length) { continue; // Skip pages without embeddings or wrong dimension }// Initialize cache table only in Val Town (bypass SQLite when running locally)const CACHE_TABLE = "groq_docs_cache_v3"; // Updated table with hash and embeddingsif (IS_VALTOWN) { contentHash TEXT, embeddings TEXT, cachedAt INTEGER NOT NULL contentHash: string | null; embeddings: number[] | null;} | null> => { const result = await sqlite.execute({ sql: `SELECT content, charCount, tokenCount, frontmatter, metadata, contentHash, embeddings FROM ${CACHE_TABLE} WHERE url = ?`, args: [url] contentHash: rowObj.contentHash as string | null, embeddings: rowObj.embeddings ? JSON.parse(rowObj.embeddings as string) : null, };export const setCache = async (url: string, data: { content: string; charCount: number; tokenCount: number | null; frontmatter: any; metadata?: any; contentHash?: string; embeddings?: number[] | null }): Promise<string | null> => { if (!IS_VALTOWN) { await sqlite.execute({ sql: `INSERT OR REPLACE INTO ${CACHE_TABLE} (url, content, charCount, tokenCount, frontmatter, metadata, contentHash, embeddings, cachedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, args: [ data.contentHash || null, data.embeddings ? JSON.stringify(data.embeddings) : null, Date.now(),```async function calculateEmbeddings(text) { const url = `https://yawnxyz-ai.web.val.run/generate?embed=true&value=${encodeURIComponent(text)}`; } catch (error) { console.error('Error calculating embeddings:', error); return null;/\/api\/health/,/\/api\/embeddings\/health/,/\/api\/chat\/health/
import chatRoutes from './routes/chat.ts';import embeddingRoutes from './routes/embeddings.ts';app.route('/api/chat', chatRoutes);app.route('/api/embeddings', embeddingRoutes);import { Hono } from 'https://esm.sh/hono@3.11.7';import { generateEmbedding, generateEmbeddings } from '../services/embeddings.ts';import { searchSimilarContent } from '../services/supabase.ts';const embeddings = new Hono();// Generate embedding for a single textembeddings.post('/generate', async (c) => { try {// Generate embeddings for multiple textsembeddings.post('/batch', async (c) => { try { const embeddings = await generateEmbeddings(texts); return c.json({ embeddings, count: embeddings.length, dimensions: embeddings[0]?.length || 0 }); return c.json({ error: 'Failed to generate batch embeddings', details: error.message // Search similar content using embeddingembeddings.post('/search', async (c) => { try {// Health check endpointembeddings.get('/health', (c) => { return c.json({ status: 'healthy', service: 'nelson-gpt-embeddings', timestamp: new Date().toISOString()export default embeddings;import { streamSSE } from 'https://esm.sh/hono@3.11.7/streaming';import { generateEmbedding } from '../services/embeddings.ts';import { searchSimilarContent } from '../services/supabase.ts';export async function generateEmbeddings(texts: string[]): Promise<number[][]> { try { const embeddings = await Promise.all( texts.map(text => generateEmbedding(text)) ); return embeddings; } catch (error) { console.error('Error generating batch embeddings:', error); throw error;// Utility function to normalize embeddings (optional, for better similarity search)export function normalizeEmbedding(embedding: number[]): number[] {// Calculate cosine similarity between two embeddingsexport function cosineSimilarity(a: number[], b: number[]): number { if (a.length !== b.length) { throw new Error('Embeddings must have the same length'); }- **AI**: Mistral API for LLM responses- **Embeddings**: Hugging Face sentence-transformers- **Animations**: Lottie│ │ ├── chat.ts # Chat API endpoints│ │ └── embeddings.ts # Embedding generation│ └── services/│ ├── mistral.ts # Mistral API client│ └── embeddings.ts # HuggingFace embeddings├── frontend/- `VITE_SUPABASE_SERVICE_ROLE_KEY`: Supabase service role key- `VITE_HUGGINFACE_API_KEY`: Hugging Face API key for embeddings// Hugging Face API for embeddingsconst HF_API_URL = "https://api-inference.huggingface.co/pipeline/feature-extraction/sentence-transformers/all-MiniLM-L6-v2";// Generate embeddings endpointapp.post("/generate", async (c) => { return c.json({ error: "Failed to generate batch embeddings", details: error.message status: "healthy", service: "embeddings", model: "sentence-transformers/all-MiniLM-L6-v2", status: "unhealthy", service: "embeddings", error: error.message USER_PREFERENCES: 'nelson_user_preferences_v1', EMBEDDINGS_CACHE: 'nelson_embeddings_cache_v1'}; // Embeddings cache for performance await sqlite.execute(` CREATE TABLE IF NOT EXISTS ${TABLES.EMBEDDINGS_CACHE} ( query_hash TEXT PRIMARY KEY, await sqlite.execute(` CREATE INDEX IF NOT EXISTS idx_embeddings_cache_access ON ${TABLES.EMBEDDINGS_CACHE}(last_accessed DESC) `);import { chatRoutes } from "./routes/chat.ts";import { embeddingRoutes } from "./routes/embeddings.ts";import { runMigrations } from "./database/migrations.ts";app.route("/api/chat", chatRoutes);app.route("/api/embeddings", embeddingRoutes); // Get embedding const embResponse = await fetch("https://api.openai.com/v1/embeddings", { method: "POST",