SPB Git
2.1 KB · 21 lines typescript
1/** AI-assisted suggestions for the editor: fetches the official page (SSRF-safe), asks the configured provider for structured output with evidence quotes, and returns it for human review. Never writes to the database. */2import { z } from "zod";3import { requireUser, jsonError, HttpError } from "@/lib/auth/guard";4import { safeFetch } from "@/lib/url";5import { getCompletionProvider, UNTRUSTED_WRAPPER, SUGGEST_SYSTEM } from "@/lib/ai/provider";6const out = z.object({ summary: z.string().max(220).optional(), capabilities: z.array(z.string().max(160)).max(8).optional(), tags: z.array(z.string().regex(/^[a-z0-9-]+$/)).max(8).optional(), evidence: z.array(z.object({ claim: z.string().max(300), quote: z.string().max(220) })).max(12).optional() });7export async function POST(req: Request) {8  try {9    await requireUser(req);10    const { url } = z.object({ url: z.string().url() }).parse(await req.json());11    const provider = getCompletionProvider(); if (!provider) throw new HttpError(503, "No AI provider configured (AI_PROVIDER=none). Lexical tooling remains available.");12    const page = await safeFetch(url, { maxBytes: 400_000 }); if (!page.ok || !page.body) throw new HttpError(400, `Could not fetch page (${page.status || page.error})`);13    const text = page.body.replace(/<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").slice(0, 20_000);14    const raw = await provider.completeJson<unknown>(SUGGEST_SYSTEM, UNTRUSTED_WRAPPER(page.finalUrl, text), "suggestion");15    const parsed = out.safeParse(raw); if (!parsed.success) throw new HttpError(502, "Provider returned an invalid structure");16    // Evidence gate: drop quotes that are not actually in the document.17    const ev = (parsed.data.evidence ?? []).filter((e) => text.toLowerCase().includes(e.quote.toLowerCase().slice(0, 60)));18    return Response.json({ provider: provider.name, model: provider.model, source: page.finalUrl, suggestion: { ...parsed.data, evidence: ev }, note: "Suggestions are drafts. Verify against the source before saving; only evidence-backed claims should be kept." });19  } catch (e) { return jsonError(e); }20}21
Files · main
Menu

Theme