Files
Momento/memento-note/app/actions/diagram.ts
Antigravity ee70e74bf5
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 5m39s
CI / Deploy production (on server) (push) Successful in 22s
fix: 5 bugs critiques de l'éditeur (Phase 1 audit)
1. replaceAll (Find & Replace) — une seule transaction ProseMirror
   au lieu d'un forEach cassé. Tous les matchs sont maintenant remplacés.

2. Link Preview unwrap — deleteNode() au lieu de clearer les attrs
   qui laissaient un nœud fantôme invisible dans le document.

3. Conversion Markdown → richtext — breaks: true dans marked.parse()
   Les simple newlines sont maintenant convertis en <br>.
   + préserve les blocs custom (toggle, callout, math, columns,
   outline, link-preview) en commentaires HTML lors de l'export MD.

4. emitNoteChange exercices — shape corrigée (type:'created' attend
   un objet Note, pas noteId/notebookId séparés).

5. Raccourcis clavier sans conflit :
   Cmd+Shift+C → Cmd+Alt+C (callout, avant: copier)
   Cmd+Shift+O → Cmd+Alt+O (outline, avant: historique/signets)
   Cmd+Shift+L → Cmd+Alt+L (colonnes, avant: lock screen macOS)
2026-06-20 15:48:18 +00:00

116 lines
5.4 KiB
TypeScript

'use server'
import { auth } from '@/auth'
import prisma from '@/lib/prisma'
import { getSystemConfig } from '@/lib/config'
import { getChatProvider } from '@/lib/ai/factory'
import { reserveUsageOrThrow } from '@/lib/entitlements'
import { toolRegistry } from '@/lib/ai/tools/registry'
// S'assurer que l'outil est importé pour s'enregistrer dans le registre
import '@/lib/ai/tools/excalidraw.tool'
const SYSTEM_PROMPT = `Tu es un architecte visuel expert dans la création de diagrammes Excalidraw extrêmement détaillés, techniques et intelligents.
Analyse le texte ou l'architecture fournie par l'utilisateur et génère un diagramme riche, complet, informatif et hautement professionnel.
Tu DOIS impérativement répondre UNIQUEMENT avec un objet JSON valide au format simplifié suivant :
{
"title": "Titre du diagramme",
"type": "auto",
"style": "default",
"zones": [
{"id": "z1", "label": "Nom de la Zone (ex: Frontend)"}
],
"nodes": [
{"id": "n1", "label": "Nom du composant/classe/fichier\\n• Propriété 1\\n• Propriété 2", "type": "rect", "zoneId": "z1"}
],
"edges": [
{"from": "n1", "to": "n2", "label": "relation"}
]
}
RÈGLES D'INTELLIGENCE ET DE RICHESSE TECHNIQUE (CRITIQUES) :
1. **Détails internes des Nœuds (CRITIQUE) :** Pour chaque fichier, classe, modèle de base de données ou service, utilise ABSOLUMENT des sauts de ligne échappés (\\n) pour lister ses attributs clés, chemins de fichiers, sous-fichiers, propriétés ou fonctions décrits dans le texte (ex: utilise des listes à puces comme \\n• Propriété 1\\n• Propriété 2). L'utilisateur doit pouvoir lire les détails techniques (ex: les champs "userId", "tier", "status" dans un modèle de base de données, ou les sous-composants comme "Embedded Checkout" et "Toggle mensuel/annuel" dans un fichier UI) DIRECTEMENT dans le corps du nœud. Ne crée JAMAIS de nœuds vides ou purement génériques !
2. **Zones & Groupements :** Utilise systématiquement des zones ("zones") pour regrouper les nœuds par couche logique ou technologique (ex: Frontend, API / Backend, Base de données, Services Externes, Cache). Renseigne "zoneId" sur chaque nœud pour le placer dans sa zone.
3. **Fidélité absolue au texte :** Intègre tous les chemins de fichiers (ex: "/settings/billing", "lib/stripe.ts", "billing-plans.tsx"), actions (ex: "POST /api/billing/webhook") et variables importants décrits.
4. **Types de nœuds :** Utilise "ellipse" pour les points d'entrée/sorties ou rôles utilisateurs, "diamond" pour les choix/décisions, et "rect" (par défaut) pour les composants, fichiers, tables et services.
5. **Format Strict :** De 5 à 15 nœuds maximum pour un diagramme d'architecture lisible. Échappe correctement tous les sauts de ligne comme \\n dans tes chaînes JSON. Ne renvoie AUCUN texte en dehors du JSON. Pas de blabla, pas de markdown.`;
function extractJsonFromText(text: string): any {
if (!text) return null
try {
const parsed = JSON.parse(text.trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
const jsonBlockRegex = /```json\s*([\s\S]*?)\s*```/i
const match = text.match(jsonBlockRegex)
if (match && match[1]) {
try {
const parsed = JSON.parse(match[1].trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
}
const braceRegex = /(\{[\s\S]*\})/
const braceMatch = text.match(braceRegex)
if (braceMatch && braceMatch[1]) {
try {
const parsed = JSON.parse(braceMatch[1].trim())
if (parsed && typeof parsed === 'object') return parsed
} catch (e) {}
}
return null
}
export async function generateDiagramFromText(text: string): Promise<{ success: boolean; canvasId?: string; error?: string }> {
const session = await auth()
if (!session?.user?.id) {
return { success: false, error: 'Non autorisé' }
}
const userId = session.user.id
try {
// 1. Vérification et déduction des quotas
await reserveUsageOrThrow(userId, 'excalidraw_generate')
// 2. Instancier le modèle de chat IA
const systemConfig = await getSystemConfig()
const provider = getChatProvider(systemConfig)
// 3. Appel du modèle
const prompt = `${SYSTEM_PROMPT}\n\nTexte à analyser :\n${text}`
const aiResponse = await provider.generateText(prompt)
// 4. Extraction du JSON
const parsedJson = extractJsonFromText(aiResponse)
if (!parsedJson || !parsedJson.nodes || !Array.isArray(parsedJson.nodes)) {
console.error('[generateDiagramFromText] Invalid JSON from AI:', aiResponse)
return { success: false, error: "L'IA n'a pas généré un format de diagramme valide. Veuillez réessayer." }
}
// 5. Invoquer le tool d'Excalidraw
const registered = toolRegistry.get('generate_excalidraw')
if (!registered) {
return { success: false, error: "Outil de génération Excalidraw non disponible." }
}
const ctx = { userId, config: systemConfig }
const toolInstance = registered.buildTool(ctx as any)
const result = await toolInstance.execute({
title: parsedJson.title || "Diagramme",
diagram: JSON.stringify(parsedJson)
})
if (!result.success || !result.canvasId) {
return { success: false, error: result.error || "La création du canevas a échoué." }
}
return { success: true, canvasId: result.canvasId }
} catch (err: any) {
console.error('[generateDiagramFromText] Error:', err)
return { success: false, error: err.message || 'Une erreur inattendue est survenue.' }
}
}