diff --git a/memento-note/app/api/brainstorm/[sessionId]/convert/route.ts b/memento-note/app/api/brainstorm/[sessionId]/convert/route.ts index c149b73..5bdcc96 100644 --- a/memento-note/app/api/brainstorm/[sessionId]/convert/route.ts +++ b/memento-note/app/api/brainstorm/[sessionId]/convert/route.ts @@ -7,6 +7,7 @@ import { emitToSession } from '@/lib/socket-emit' const convertSchema = z.object({ ideaId: z.string().min(1), + notebookId: z.string().min(1).nullable().optional(), }) export async function POST( @@ -21,7 +22,7 @@ export async function POST( try { const { sessionId } = await params const body = await request.json() - const { ideaId } = convertSchema.parse(body) + const { ideaId, notebookId: paramNotebookId } = convertSchema.parse(body) const brainstormSession = await prisma.brainstormSession.findFirst({ where: { @@ -51,7 +52,7 @@ export async function POST( } let sourceSection = '' - let targetNotebookId: string | null = null + let targetNotebookId: string | null = paramNotebookId ?? null if (brainstormSession.exportedNoteId) { const exportedNote = await prisma.note.findUnique({ diff --git a/memento-note/components/brainstorm/brainstorm-confirm-dialog.tsx b/memento-note/components/brainstorm/brainstorm-confirm-dialog.tsx new file mode 100644 index 0000000..a65df84 --- /dev/null +++ b/memento-note/components/brainstorm/brainstorm-confirm-dialog.tsx @@ -0,0 +1,181 @@ +'use client' + +import { useState, memo } from 'react' +import { motion, AnimatePresence } from 'motion/react' +import { Wind, FolderOpen, X, Loader2, ChevronDown } from 'lucide-react' +import { useLanguage } from '@/lib/i18n' +import { useNotebooks } from '@/context/notebooks-context' +import type { Note } from '@/lib/types' + +interface BrainstormConfirmDialogProps { + open: boolean + onClose: () => void + onConfirm: (notebookId: string | null) => void + note: Note | null +} + +function BrainstormConfirmDialogInner({ open, onClose, onConfirm, note }: BrainstormConfirmDialogProps) { + const { t } = useLanguage() + const { notebooks } = useNotebooks() + const [showNotebookSelect, setShowNotebookSelect] = useState(false) + + const noteNotebook = notebooks.find(nb => nb.id === note?.notebookId) + const defaultNotebookName = noteNotebook?.name || t('brainstorm.defaultNotebook') || 'Brainstorm' + const defaultNotebookId = note?.notebookId ?? null + + const [selectedNotebookId, setSelectedNotebookId] = useState(defaultNotebookId) + const [selectedNotebookName, setSelectedNotebookName] = useState(defaultNotebookName) + const [loading, setLoading] = useState(false) + + const handleConfirm = () => { + setLoading(true) + onConfirm(selectedNotebookId) + } + + const handleClose = () => { + setLoading(false) + setSelectedNotebookId(defaultNotebookId) + setSelectedNotebookName(defaultNotebookName) + setShowNotebookSelect(false) + onClose() + } + + return ( + + {open && note && ( + { if (e.target === e.currentTarget) handleClose() }} + role="dialog" + aria-modal="true" + aria-label={t('brainstorm.confirmTitle') || 'Brainstorm this idea'} + > + e.stopPropagation()} + > + {/* Header */} +
+
+
+ +
+
+

+ {t('brainstorm.confirmTitle') || 'Brainstorm this idea'} +

+

+ {t('brainstorm.confirmSubtitle') || 'AI will expand your idea'} +

+
+
+ +
+ + {/* Body */} +
+ {/* Seed preview */} +
+

+ {t('brainstorm.seedLabel') || 'Seed idea'} +

+

+ {note.title || t('notes.untitled')} +

+
+ + {/* Destination notebook */} +
+

+ {t('brainstorm.destinationLabel') || 'Notes will be saved to'} +

+ + + {showNotebookSelect && ( + +
+ + {notebooks.map(nb => ( + + ))} +
+
+ )} +
+
+
+ + {/* Footer */} +
+ + +
+
+
+ )} +
+ ) +} + +export const BrainstormConfirmDialog = memo(BrainstormConfirmDialogInner) diff --git a/memento-note/components/brainstorm/brainstorm-page.tsx b/memento-note/components/brainstorm/brainstorm-page.tsx index fc368c7..1b7e095 100644 --- a/memento-note/components/brainstorm/brainstorm-page.tsx +++ b/memento-note/components/brainstorm/brainstorm-page.tsx @@ -106,8 +106,9 @@ export function BrainstormPage() { const { data: sessions, isLoading: sessionsLoading } = useBrainstormSessions() - // Auto-sélectionner la dernière session si aucune session active + // Auto-sélectionner la dernière session si aucune session active ET pas de seed dans l'URL useEffect(() => { + if (urlSeed || urlInviteToken) return // Ne pas auto-select si on arrive avec un seed if (!activeSessionId && !urlSessionId && !sessionsLoading && sessions && sessions.length > 0) { const last = sessions[0] setActiveSessionId(last.id) @@ -243,9 +244,9 @@ export function BrainstormPage() { const handleConvert = async (idea: BrainstormIdea) => { try { - const result = await convertIdea.mutateAsync(idea.id) + const result = await convertIdea.mutateAsync({ ideaId: idea.id, notebookId: null }) if (result?.id) { - toast.success(t('brainstorm.noteCreated') || 'Note created', { + toast.success(t('brainstorm.noteCreatedIn') || 'Note created', { description: result.title || idea.title, action: { label: t('notePeek.openFully') || 'Open', diff --git a/memento-note/components/notes-list-views.tsx b/memento-note/components/notes-list-views.tsx index f6e1a69..a866924 100644 --- a/memento-note/components/notes-list-views.tsx +++ b/memento-note/components/notes-list-views.tsx @@ -50,6 +50,8 @@ import { } from 'lucide-react' import { cn } from '@/lib/utils' import { useHydrated } from '@/lib/use-hydrated' +import dynamic from 'next/dynamic' +const BrainstormConfirmDialog = dynamic(() => import('@/components/brainstorm/brainstorm-confirm-dialog').then(m => ({ default: m.BrainstormConfirmDialog })), { ssr: false }) import { formatDistanceToNow } from 'date-fns' import { fr } from 'date-fns/locale/fr' import { enUS } from 'date-fns/locale/en-US' @@ -715,14 +717,23 @@ const GridCard = memo(function GridCard({ aiIllustrationEnabled, onOpen, onTogglePin, - onDeleteNote, - onMoveToNotebook, + onDeleteNote, onMoveToNotebook, onNoteIllustrationGenerated, onNoteIllustrationDeleted, onOpenHistory, isOverlay = false, }: GridCardSharedProps) { const router = useRouter() + const [brainstormNote, setBrainstormNote] = useState(null) + + const handleBrainstormConfirm = (notebookId: string | null) => { + if (!brainstormNote) return + const seed = `${brainstormNote.title || untitled}\n\n${getNotePlainExcerpt(brainstormNote, 200)}`.trim() + const params = new URLSearchParams({ seed: seed.slice(0, 300), sourceNoteId: brainstormNote.id }) + if (notebookId) params.set('notebookId', notebookId) + router.push(`/brainstorm?${params.toString()}`) + setBrainstormNote(null) + } const { t, language } = useLanguage() const hydrated = useHydrated() const title = getNoteDisplayTitle(note, untitled) @@ -741,11 +752,11 @@ const GridCard = memo(function GridCard({ const handleBrainstormClick = (e: React.MouseEvent) => { e.stopPropagation() - const seed = `${title}\n\n${excerpt}`.trim() || title - router.push(`/brainstorm?seed=${encodeURIComponent(seed.slice(0, 300))}&sourceNoteId=${note.id}`) + setBrainstormNote(note) } return ( + <> + + setBrainstormNote(null)} + onConfirm={(notebookId) => { if (brainstormNote) handleBrainstormConfirm(notebookId) }} + note={brainstormNote} + /> + ) }) diff --git a/memento-note/hooks/use-brainstorm.ts b/memento-note/hooks/use-brainstorm.ts index 4f8c3d1..7493e84 100644 --- a/memento-note/hooks/use-brainstorm.ts +++ b/memento-note/hooks/use-brainstorm.ts @@ -154,12 +154,14 @@ export function useConvertIdea(sessionId: string) { const queryClient = useQueryClient() return useMutation({ - mutationFn: async (ideaId: string) => { + mutationFn: async (args: string | { ideaId: string; notebookId?: string | null }) => { + const ideaId = typeof args === 'string' ? args : args.ideaId + const notebookId = typeof args === 'string' ? null : args.notebookId const res = await fetch(`/api/brainstorm/${sessionId}/convert`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ ideaId }), + body: JSON.stringify({ ideaId, notebookId }), }) const data = await res.json() if (!data.success) throw new Error(data.error || 'Failed to convert idea') diff --git a/memento-note/locales/en.json b/memento-note/locales/en.json index cc7baaf..b4ecd00 100644 --- a/memento-note/locales/en.json +++ b/memento-note/locales/en.json @@ -2925,7 +2925,7 @@ "noSessions": "No brainstorms yet", "startOne": "Start one", "sessions": "Brainstorms", - "seedLabel": "Seed Idea", + "seedLabel": "Seed idea", "ideaPromptDetailed": "Enter your idea, question, or topic to brainstorm...", "brainstormThisIdea": "Brainstorm this idea", "startBrainstorm": "Start Brainstorm", @@ -3068,7 +3068,15 @@ "legendWave1": "Variations", "legendWave2": "Analogies", "legendWave3": "Disruptions", - "legendConverted": "Converted" + "legendConverted": "Converted", + "confirmTitle": "Brainstorm this idea", + "confirmSubtitle": "AI will expand your idea", + "destinationLabel": "Notes will be saved to", + "defaultNotebook": "Brainstorm", + "noNotebook": "No notebook", + "launching": "Launching…", + "launch": "Brainstorm", + "noteCreatedIn": "Note created in" }, "byokSettings": { "title": "Your API keys (BYOK)", diff --git a/memento-note/locales/fr.json b/memento-note/locales/fr.json index 5c36f79..cde8ce0 100644 --- a/memento-note/locales/fr.json +++ b/memento-note/locales/fr.json @@ -3072,7 +3072,15 @@ "legendWave1": "Variations", "legendWave2": "Analogies", "legendWave3": "Disruptions", - "legendConverted": "Convertie" + "legendConverted": "Convertie", + "confirmTitle": "Brainstormer cette idée", + "confirmSubtitle": "L'IA va développer votre idée", + "destinationLabel": "Les notes seront sauvegardées dans", + "defaultNotebook": "Brainstorm", + "noNotebook": "Aucun carnet", + "launching": "Lancement…", + "launch": "Brainstormer", + "noteCreatedIn": "Note créée dans" }, "byokSettings": { "title": "Vos clés API (BYOK)",