diff --git a/memento-note/app/api/ai/generate-exercises/route.ts b/memento-note/app/api/ai/generate-exercises/route.ts index 426db77..d555a3b 100644 --- a/memento-note/app/api/ai/generate-exercises/route.ts +++ b/memento-note/app/api/ai/generate-exercises/route.ts @@ -3,6 +3,7 @@ import { auth } from '@/auth' import prisma from '@/lib/prisma' import { exerciseGeneratorService } from '@/lib/ai/services/exercise-generator.service' import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements' +import { preprocessMathInHtml } from '@/lib/text/math-preprocess' export async function POST(request: NextRequest) { try { @@ -54,13 +55,15 @@ export async function POST(request: NextRequest) { const ex = exercises[i] const difficultyEmoji = ex.difficulty === 'facile' ? '🟱' : ex.difficulty === 'moyen' ? '🟡' : '🔮' - const content = ` + const rawContent = `

${exerciseLabel} ${i + 1} — ${difficultyEmoji} ${ex.difficulty}

ÉnoncĂ©

${ex.question}

${answerLabel} — cliquer pour rĂ©vĂ©ler

Solution

${ex.answer}

`.trim() + const content = preprocessMathInHtml(rawContent) + const created = await prisma.note.create({ data: { title: `${exerciseLabel} ${i + 1} — ${note.title || ''}`, diff --git a/memento-note/components/contextual-ai-chat.tsx b/memento-note/components/contextual-ai-chat.tsx index c900bd0..bf0ccc8 100644 --- a/memento-note/components/contextual-ai-chat.tsx +++ b/memento-note/components/contextual-ai-chat.tsx @@ -218,7 +218,7 @@ export function ContextualAIChat({ const [translateTarget, setTranslateTarget] = useState('') // Generate slides / diagram state - const [generateLoading, setGenerateLoading] = useState<'slides' | 'diagram' | 'exercises' | null>(null) + const [generateLoading, setGenerateLoading] = useState<'slides' | 'diagram' | null>(null) const [generateProgress, setGenerateProgress] = useState(0) const [generateResult, setGenerateResult] = useState(null) const [customLangInput, setCustomLangInput] = useState('') @@ -1228,54 +1228,6 @@ export function ContextualAIChat({ - {/* ── GĂ©nĂ©rateur d'exercices ── */} - {noteId && ( -
-
- -
-
-
-
-
-
{t('ai.generate.exercises') || 'Générer des exercices'}
-

{t('ai.generate.exercisesHint') || '5 exercices avec corrigés'}

-
-
-

- {t('ai.generate.exercisesDesc') || "L'IA crée 5 exercices basés sur cette note, avec des niveaux de difficulté variés et des corrigés détaillés."} -

- -
-
- )} - {/* ── Personas IA ── */} diff --git a/memento-note/components/note-editor/note-editor-toolbar.tsx b/memento-note/components/note-editor/note-editor-toolbar.tsx index d5073f6..cf48aa9 100644 --- a/memento-note/components/note-editor/note-editor-toolbar.tsx +++ b/memento-note/components/note-editor/note-editor-toolbar.tsx @@ -42,7 +42,7 @@ interface NoteEditorToolbarProps { export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachmentsCount }: NoteEditorToolbarProps) { const { state, actions, note, readOnly, fullPage, notebooks, fileInputRef, richTextEditorRef } = useNoteEditorContext() - const { t } = useLanguage() + const { t, language } = useLanguage() const [isConverting, setIsConverting] = useState(false) const [shareOpen, setShareOpen] = useState(false) const [flashcardsOpen, setFlashcardsOpen] = useState(false) @@ -236,6 +236,7 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme } const [generatingExercises, setGeneratingExercises] = useState(false) + const [showEduMenu, setShowEduMenu] = useState(false) const handleGenerateExercises = async () => { if (generatingExercises) return setGeneratingExercises(true) @@ -249,7 +250,16 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme if (!res.ok) { toast.error(data.errorKey === 'ai.featureLocked' ? (t('ai.featureLocked') || 'Plan requis') : (data.error || 'Erreur')) } else { - toast.success(t('richTextEditor.exercisesGenerated') || `${data.exercises?.length || 0} exercices créés !`) + toast.success(`${data.exercises?.length || 0} ${t('richTextEditor.exercisesGenerated') || 'exercices créés dans ce carnet !'}`, { + action: { + label: t('richTextEditor.seeExercises') || 'Voir', + onClick: () => window.location.reload(), + }, + }) + // Emit events so the note list refreshes + for (const ex of data.exercises || []) { + emitNoteChange({ type: 'created', noteId: ex.id, notebookId: note.notebookId }) + } } } catch (e: any) { toast.error(e.message || 'Erreur') @@ -389,14 +399,48 @@ export function NoteEditorToolbar({ mode, onClose, onToggleAttachments, attachme {!readOnly && ( - +
+ + {showEduMenu && ( + <> +
setShowEduMenu(false)} /> +
+ + +
+ + )} +
)} {!readOnly && voiceSupported && ( diff --git a/memento-note/lib/ai/services/notebook-wizard.service.ts b/memento-note/lib/ai/services/notebook-wizard.service.ts index 81880c1..c5d1ec6 100644 --- a/memento-note/lib/ai/services/notebook-wizard.service.ts +++ b/memento-note/lib/ai/services/notebook-wizard.service.ts @@ -1,5 +1,6 @@ import { getChatProvider } from '../factory' import { getSystemConfig } from '@/lib/config' +import { preprocessMathInHtml } from '@/lib/text/math-preprocess' export interface GeneratedNote { title: string @@ -173,40 +174,3 @@ Les "difficulty" doivent varier : mĂ©lange facile/moyen/difficile.` } export const notebookWizardService = new NotebookWizardService() - -/** - * Convertit les dĂ©limiteurs LaTeX ($$...$$ et $...$) en nƓuds TipTap - * pour que les Ă©quations s'affichent correctement au chargement. - */ -function preprocessMathInHtml(html: string): string { - let result = html - - // 1. \[...\] → block math - result = result.replace(/\\\[([\s\S]+?)\\\]/g, (_, latex) => { - const escaped = latex.trim().replace(/"/g, '"') - return `

` - }) - - // 2. $$...$$ → block math - result = result.replace(/\$\$([\s\S]+?)\$\$/g, (_, latex) => { - const escaped = latex.trim().replace(/"/g, '"') - return `

` - }) - - // 3. \(...\) → inline math - result = result.replace(/\\\(([\s\S]+?)\\\)/g, (_, latex) => { - const escaped = latex.trim().replace(/"/g, '"') - return `${escaped}` - }) - - // 4. $...$ → inline math (only single $ not followed by another $) - result = result.replace(/(? { - const escaped = latex.trim().replace(/"/g, '"') - return `${escaped}` - }) - - // 5. Clean up empty

tags - result = result.replace(/

\s*<\/p>/g, '') - - return result -} diff --git a/memento-note/lib/text/math-preprocess.ts b/memento-note/lib/text/math-preprocess.ts new file mode 100644 index 0000000..167bdf6 --- /dev/null +++ b/memento-note/lib/text/math-preprocess.ts @@ -0,0 +1,36 @@ +/** + * Convertit les dĂ©limiteurs LaTeX en nƓuds TipTap pour le rendu KaTeX. + * À appliquer sur tout contenu HTML gĂ©nĂ©rĂ© par l'IA avant stockage en DB. + */ +export function preprocessMathInHtml(html: string): string { + let result = html + + // 1. \[...\] → block math + result = result.replace(/\\\[([\s\S]+?)\\\]/g, (_, latex) => { + const escaped = latex.trim().replace(/"/g, '"') + return `

` + }) + + // 2. $$...$$ → block math + result = result.replace(/\$\$([\s\S]+?)\$\$/g, (_, latex) => { + const escaped = latex.trim().replace(/"/g, '"') + return `

` + }) + + // 3. \(...\) → inline math + result = result.replace(/\\\(([\s\S]+?)\\\)/g, (_, latex) => { + const escaped = latex.trim().replace(/"/g, '"') + return `${escaped}` + }) + + // 4. $...$ → inline math (only single $ not followed by another $) + result = result.replace(/(? { + const escaped = latex.trim().replace(/"/g, '"') + return `${escaped}` + }) + + // 5. Clean up empty

tags + result = result.replace(/

\s*<\/p>/g, '') + + return result +} diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index 3576bc2..8cc66c8 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -2559,6 +2559,7 @@ "pdfExportLoading": "Generating PDF...", "pdfExportSuccess": "PDF ready!", "generateExercises": "Generate exercises", + "generateExercisesHint": "5 exercises + answers", "exercisesLoading": "Generating exercises...", "exercisesGenerated": "exercises created!", "aiGenerateExercises": "Generate exercises", diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index 9ebade7..2afb6ac 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -2563,6 +2563,7 @@ "pdfExportLoading": "GĂ©nĂ©ration du PDF...", "pdfExportSuccess": "PDF prĂȘt !", "generateExercises": "GĂ©nĂ©rer des exercices", + "generateExercisesHint": "5 exercices + corrigĂ©s", "exercisesLoading": "GĂ©nĂ©ration des exercices...", "exercisesGenerated": "exercices créés !", "aiGenerateExercises": "GĂ©nĂ©rer des exercices", diff --git a/memento-note/tests/unit/chunk-indexing.test.ts b/memento-note/tests/migration/chunk-indexing.test.ts similarity index 100% rename from memento-note/tests/unit/chunk-indexing.test.ts rename to memento-note/tests/migration/chunk-indexing.test.ts