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)
116 lines
5.4 KiB
TypeScript
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.' }
|
|
}
|
|
}
|