'use server' import DOMPurify from 'isomorphic-dompurify' import { auth } from '@/auth' import { prisma } from '@/lib/prisma' import { getAIProvider } from '@/lib/ai/factory' import { getSystemConfig } from '@/lib/config' import { getAISettings } from '@/app/actions/ai-settings' import { revalidatePath } from 'next/cache' function extractSvgSnippet(raw: string): string | null { const trimmed = raw.trim() const fenced = trimmed.match(/```(?:svg)?\s*([\s\S]*?)```/i) const candidate = (fenced ? fenced[1] : trimmed).trim() const start = candidate.indexOf('') if (start === -1 || end === -1 || end <= start) return null return candidate.slice(start, end + 6) } function sanitizeSvgMarkup(svg: string): string { return DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true }, ADD_TAGS: ['use'], ADD_ATTR: ['viewBox', 'xmlns', 'preserveAspectRatio'], }) } /** * Génère une miniature SVG abstraite pour le flux éditorial (via modèle chat configuré). * Respecte les préférences utilisateur (assistant IA activé) et nettoie le SVG. */ export async function generateNoteIllustrationSvg(noteId: string): Promise<{ ok: true } | { ok: false; error: string }> { const session = await auth() if (!session?.user?.id) return { ok: false, error: 'Non autorisé' } try { const settings = await getAISettings(session.user.id) if (settings.paragraphRefactor === false) { return { ok: false, error: 'Assistant IA désactivé dans vos paramètres.' } } const note = await prisma.note.findFirst({ where: { id: noteId, userId: session.user.id }, select: { id: true, title: true, content: true }, }) if (!note) return { ok: false, error: 'Note introuvable' } const plainTitle = (note.title || '').slice(0, 200) const plainBody = note.content .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim() .slice(0, 1200) if (!plainBody && !plainTitle) { return { ok: false, error: 'Ajoutez du contenu avant de générer une illustration.' } } const config = await getSystemConfig() const provider = getAIProvider(config) const prompt = `Tu es un designer minimaliste. Produis UN SEUL document SVG valide pour une vignette de carte note. Contraintes strictes: - viewBox="0 0 224 168" (rapport 4:3), pas de width/height fixes en px sur la racine ou width="100%" height="100%" - Style architectural / papier, 2–4 formes géométriques ou lignes, palette sobre (noir/gris/une couleur douce), pas de texte lisible - AUCUN script, AUCUNE balise foreignObject, AUCUN lien externe, AUCUN attribut on* - Réponds UNIQUEMENT avec le fragment SVG (commence par et finit par ), sans markdown ni commentaire. Thème à suggérer visuellement (abstrait, pas littéral): Titre: ${plainTitle || '(sans titre)'} Extrait: ${plainBody.slice(0, 400)}` const raw = await provider.generateText(prompt) const extracted = extractSvgSnippet(raw) if (!extracted) { return { ok: false, error: 'Le modèle n’a pas renvoyé un SVG valide. Réessayez.' } } const safe = sanitizeSvgMarkup(extracted) if (!safe.includes('