spb/chat-spboucher-ai
Public
1import { z } from "zod";2import { guarded, json, body } from "@/lib/api";3import { sql, vec } from "@/lib/db";4import { newId } from "@/lib/ids";5import { openaiEmbed } from "@/lib/ai/openai";67export const GET = guarded(async (req) => {8 const p = req.nextUrl.searchParams;9 const status = p.get("status"); const q = p.get("q");10 const memories = await sql`SELECT m.*, c.title AS source_title FROM memories m LEFT JOIN conversations c ON c.id = m.source_conversation_id11 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`;12 const stats = await sql`SELECT status, count(*)::int AS n FROM memories GROUP BY status`;13 return json({ memories: memories.map(m => ({ ...m, embedding: undefined })), stats });14});1516export const POST = guarded(async (req) => {17 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() }));18 const id = newId("mem");19 let emb: number[] | null = null; try { emb = (await openaiEmbed([b.content])).vectors[0]; } catch { emb = null; }20 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})`;21 return json({ id }, { status: 201 });22});23