import { z } from "zod"; import { guarded, json, body } from "@/lib/api"; import { sql, vec } from "@/lib/db"; import { newId } from "@/lib/ids"; import { openaiEmbed } from "@/lib/ai/openai"; export const GET = guarded(async (req) => { const p = req.nextUrl.searchParams; const status = p.get("status"); const q = p.get("q"); const memories = await sql`SELECT m.*, c.title AS source_title FROM memories m LEFT JOIN conversations c ON c.id = m.source_conversation_id WHERE (${!status} OR m.status = ${status ?? ""}) AND (${!q} OR m.content ILIKE ${"%" + (q ?? "") + "%"}) ORDER BY m.status = 'candidate' DESC, m.updated_at DESC LIMIT 500`; const stats = await sql`SELECT status, count(*)::int AS n FROM memories GROUP BY status`; return json({ memories: memories.map(m => ({ ...m, embedding: undefined })), stats }); }); export const POST = guarded(async (req) => { const b = await body(req, z.object({ content: z.string().min(3).max(2000), category: z.string().max(30).optional(), projectId: z.string().nullable().optional() })); const id = newId("mem"); let emb: number[] | null = null; try { emb = (await openaiEmbed([b.content])).vectors[0]; } catch { emb = null; } await sql`INSERT INTO memories (id, category, content, source, project_id, status, embedding) VALUES (${id}, ${b.category ?? "context"}, ${b.content}, 'manual', ${b.projectId ?? null}, 'active', ${emb ? sql`${vec(emb)}::vector` : null})`; return json({ id }, { status: 201 }); });