fix: exercices dans menu GraduationCap + équations KaTeX + refresh liste
- Menu déroulant GraduationCap : Flashcards + Exercices réunis - Fix: language non défini dans toolbar (useLanguage destructuring) - Fix: équations 658071 → KaTeX dans exercices (preprocessMathInHtml partagé) - lib/text/math-preprocess.ts : utilitaire partagé wizard + exercices - Toast avec bouton 'Voir' pour rafraîchir après création exercices - emitNoteChange pour rafraîchir la liste - i18n FR/EN
This commit is contained in:
@@ -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<GenerateResult | null>(null)
|
||||
const [customLangInput, setCustomLangInput] = useState('')
|
||||
@@ -1228,54 +1228,6 @@ export function ContextualAIChat({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── Générateur d'exercices ── */}
|
||||
{noteId && (
|
||||
<div className="group relative p-6 rounded-2xl bg-white border border-border hover:border-brand-accent/30 transition-all duration-500 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
|
||||
<PenTool size={80} className="text-brand-accent" />
|
||||
</div>
|
||||
<div className="relative space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-slate-50 rounded-lg text-brand-accent"><PenTool size={18} /></div>
|
||||
<div className="space-y-0.5">
|
||||
<h5 className="text-sm font-bold text-foreground leading-none">{t('ai.generate.exercises') || 'Générer des exercices'}</h5>
|
||||
<p className="text-[9px] text-foreground/40 uppercase tracking-tight">{t('ai.generate.exercisesHint') || '5 exercices avec corrigés'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-[10px] text-foreground/50 leading-relaxed">
|
||||
{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."}
|
||||
</p>
|
||||
<button
|
||||
onClick={async () => {
|
||||
if (!noteId) return
|
||||
setGenerateLoading('exercises')
|
||||
try {
|
||||
const res = await fetch('/api/ai/generate-exercises', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ noteId, count: 5, language: t('ai.language') === 'فارسی' ? 'fa' : 'fr' }),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (res.ok && data.exercises) {
|
||||
toast.success(`${data.exercises.length} ${t('ai.generate.exercisesCreated') || 'exercices créés !'}`)
|
||||
} else {
|
||||
toast.error(data.error || 'Erreur')
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e.message || 'Erreur')
|
||||
} finally {
|
||||
setGenerateLoading(null)
|
||||
}
|
||||
}}
|
||||
disabled={!!generateLoading}
|
||||
className="w-full py-3.5 bg-brand-accent text-white rounded-xl text-[10px] font-bold flex items-center justify-center gap-2 hover:opacity-90 transition-all shadow-lg shadow-brand-accent/20 uppercase tracking-[0.2em] disabled:opacity-50"
|
||||
>
|
||||
{generateLoading === 'exercises' ? <Loader2 size={14} className="animate-spin" /> : <><PenTool size={14} className="opacity-80" /> {t('ai.generate.generateExercises') || 'Générer'}</>}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* ── Personas IA ── */}
|
||||
<PersonasPanel noteTitle={noteTitle} noteContent={noteContent} />
|
||||
|
||||
|
||||
@@ -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
|
||||
</button>
|
||||
|
||||
{!readOnly && (
|
||||
<button
|
||||
title={t('flashcards.toolbarGenerate')}
|
||||
aria-label={t('flashcards.toolbarGenerate')}
|
||||
onClick={() => setFlashcardsOpen(true)}
|
||||
className="p-1.5 rounded-full border border-black/20 dark:border-white/20 text-foreground hover:bg-black/5 dark:hover:bg-white/5 transition-all"
|
||||
>
|
||||
<GraduationCap size={16} />
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
title={t('flashcards.toolbarGenerate')}
|
||||
aria-label={t('flashcards.toolbarGenerate')}
|
||||
onClick={() => setShowEduMenu(v => !v)}
|
||||
className="p-1.5 rounded-full border border-black/20 dark:border-white/20 text-foreground hover:bg-black/5 dark:hover:bg-white/5 transition-all"
|
||||
>
|
||||
<GraduationCap size={16} />
|
||||
</button>
|
||||
{showEduMenu && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setShowEduMenu(false)} />
|
||||
<div className="absolute top-full right-0 mt-1 z-50 w-56 rounded-xl border border-border bg-card shadow-xl overflow-hidden">
|
||||
<button
|
||||
onClick={() => { setShowEduMenu(false); setFlashcardsOpen(true) }}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-muted transition-colors text-left"
|
||||
>
|
||||
<div className="p-1.5 rounded-lg bg-purple-50 dark:bg-purple-950/30 text-purple-600 dark:text-purple-400">
|
||||
<GraduationCap size={16} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">{t('flashcards.toolbarGenerate')}</div>
|
||||
<div className="text-[10px] text-muted-foreground">{t('flashcards.toolbarGenerateHint') || 'Révision espacée SM-2'}</div>
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => { setShowEduMenu(false); handleGenerateExercises() }}
|
||||
disabled={generatingExercises}
|
||||
className="w-full flex items-center gap-3 px-4 py-3 hover:bg-muted transition-colors text-left border-t border-border/30"
|
||||
>
|
||||
<div className="p-1.5 rounded-lg bg-brand-accent/10 text-brand-accent">
|
||||
{generatingExercises ? <Loader2Icon size={16} className="animate-spin" /> : <PenTool size={16} />}
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">{t('richTextEditor.generateExercises') || 'Générer des exercices'}</div>
|
||||
<div className="text-[10px] text-muted-foreground">{t('richTextEditor.generateExercisesHint') || '5 exercices + corrigés'}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!readOnly && voiceSupported && (
|
||||
|
||||
Reference in New Issue
Block a user