diff --git a/memento-note/app/actions/notes.ts b/memento-note/app/actions/notes.ts index 560897d..f7f2825 100644 --- a/memento-note/app/actions/notes.ts +++ b/memento-note/app/actions/notes.ts @@ -436,14 +436,12 @@ export async function commitNoteHistory(noteId: string) { const session = await auth() if (!session?.user?.id) throw new Error('Unauthorized') - const enabled = await isNoteHistoryEnabledForUser(session.user.id) - if (!enabled) throw new Error('History is disabled') - const note = await prisma.note.findFirst({ where: { id: noteId, userId: session.user.id }, - select: { id: true }, + select: { id: true, historyEnabled: true }, }) if (!note) throw new Error('Note not found') + if (!note.historyEnabled) throw new Error('History is disabled for this note') await createNoteHistorySnapshot({ noteId: note.id, @@ -466,6 +464,22 @@ export async function deleteNoteHistoryEntry(noteId: string, historyEntryId: str }) } +export async function enableNoteHistory(noteId: string) { + const session = await auth() + if (!session?.user?.id) throw new Error('Unauthorized') + + const note = await prisma.note.findFirst({ + where: { id: noteId, userId: session.user.id }, + select: { id: true }, + }) + if (!note) throw new Error('Note not found') + + await prisma.note.update({ + where: { id: noteId }, + data: { historyEnabled: true }, + }) +} + // Search notes - DB-side filtering (fast) with optional semantic search // Supports contextual search within notebook (IA5) export async function searchNotes(query: string, useSemantic: boolean = false, notebookId?: string) { @@ -641,14 +655,9 @@ export async function createNote(data: { } try { - const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id) - if (historyEnabled) { - await createNoteHistorySnapshot({ - noteId: note.id, - userId: session.user.id, - reason: 'create', - }) - } + // New notes start with historyEnabled=false (schema default), + // so no initial snapshot is needed here. + // History is enabled per-note via enableNoteHistory() action. } catch (snapshotError) { console.error('[HISTORY] Failed to create initial snapshot:', snapshotError) } @@ -783,7 +792,7 @@ export async function updateNote(id: string, data: { try { const oldNote = await prisma.note.findUnique({ where: { id, userId: session.user.id }, - select: { labels: true, notebookId: true, reminder: true, content: true, title: true } + select: { labels: true, notebookId: true, reminder: true, content: true, title: true, historyEnabled: true } }) const oldLabels: string[] = oldNote?.labels ? JSON.parse(oldNote.labels) : [] const oldNotebookId = oldNote?.notebookId @@ -854,7 +863,7 @@ export async function updateNote(id: string, data: { } try { - const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id) + const historyEnabled = oldNote?.historyEnabled === true if (historyEnabled && shouldCaptureHistorySnapshot(data as Record)) { const mode = await getNoteHistoryMode(session.user.id) if (mode === 'manual') { diff --git a/memento-note/app/api/notes/[id]/route.ts b/memento-note/app/api/notes/[id]/route.ts index 4e43577..de0c05d 100644 --- a/memento-note/app/api/notes/[id]/route.ts +++ b/memento-note/app/api/notes/[id]/route.ts @@ -142,7 +142,7 @@ export async function PUT( }) try { - const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id) + const historyEnabled = existingNote.historyEnabled === true if (historyEnabled && shouldCaptureHistorySnapshot(updateData)) { const mode = await getNoteHistoryMode(session.user.id) if (mode === 'auto') { diff --git a/memento-note/components/ai-chat.tsx b/memento-note/components/ai-chat.tsx index 63732c2..39b0d66 100644 --- a/memento-note/components/ai-chat.tsx +++ b/memento-note/components/ai-chat.tsx @@ -13,6 +13,7 @@ import { useWebSearchAvailable } from '@/hooks/use-web-search-available' import { useNotebooks } from '@/context/notebooks-context' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { toast } from 'sonner' +import { createConversation } from '@/app/actions/chat-actions' function getTextContent(msg: UIMessage): string { if (msg.parts && Array.isArray(msg.parts)) { @@ -68,6 +69,20 @@ export function AIChat() { const text = input.trim() if (!text || isLoading) return setInput('') + + // Create conversation upfront so we have the ID for continuity + let convId = conversationId + if (!convId) { + try { + const result = await createConversation(text, chatScope !== 'all' ? chatScope : undefined) + convId = result.id + setConversationId(convId) + } catch { + toast.error(t('chat.createError')) + return + } + } + await sendMessage( { text }, { @@ -76,7 +91,7 @@ export function AIChat() { chatScope, notebookId: chatScope !== 'all' ? chatScope : undefined, webSearch: webSearch && webSearchAvailable, - conversationId, + conversationId: convId, language, } } diff --git a/memento-note/components/chat/chat-container.tsx b/memento-note/components/chat/chat-container.tsx index 9cc98c6..fac1e45 100644 --- a/memento-note/components/chat/chat-container.tsx +++ b/memento-note/components/chat/chat-container.tsx @@ -50,6 +50,13 @@ export function ChatContainer({ initialConversations, notebooks, webSearchAvaila const isLoading = status === 'submitted' || status === 'streaming' + const refreshConversations = useCallback(async () => { + try { + const updated = await getConversations() + setConversations(updated) + } catch {} + }, []) + // Timeout warning: show toast if response takes > 30s useEffect(() => { if (!isLoading) return @@ -105,13 +112,6 @@ export function ChatContainer({ initialConversations, notebooks, webSearchAvaila // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentId]) - const refreshConversations = useCallback(async () => { - try { - const updated = await getConversations() - setConversations(updated) - } catch {} - }, []) - const handleSendMessage = async (content: string, notebookId?: string) => { if (notebookId) { setSelectedNotebook(notebookId) diff --git a/memento-note/components/home-client.tsx b/memento-note/components/home-client.tsx index 982c494..55ed711 100644 --- a/memento-note/components/home-client.tsx +++ b/memento-note/components/home-client.tsx @@ -4,8 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useSearchParams, useRouter } from 'next/navigation' import dynamic from 'next/dynamic' import { Note } from '@/lib/types' -import { updateAISettings } from '@/app/actions/ai-settings' -import { getAllNotes, searchNotes } from '@/app/actions/notes' +import { getAllNotes, searchNotes, enableNoteHistory } from '@/app/actions/notes' import { NoteInput } from '@/components/note-input' import { NotesMainSection, type NotesViewMode } from '@/components/notes-main-section' import { NotesViewToggle } from '@/components/notes-view-toggle' @@ -62,7 +61,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) { initialNotes.filter(n => n.isPinned) ) const [notesViewMode, setNotesViewMode] = useState(initialSettings.notesViewMode) - const [noteHistoryEnabled, setNoteHistoryEnabled] = useState(initialSettings.noteHistory) const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode) const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null) const [isLoading, setIsLoading] = useState(false) // false by default — data is pre-loaded @@ -145,9 +143,12 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) { setHistoryOpen(true) }, []) - const handleEnableHistory = useCallback(async () => { - await updateAISettings({ noteHistory: true }) - setNoteHistoryEnabled(true) + const handleEnableHistory = useCallback(async (noteId: string) => { + await enableNoteHistory(noteId) + // Update the specific note in state + setNotes((prev) => prev.map((n) => (n.id === noteId ? { ...n, historyEnabled: true } : n))) + setPinnedNotes((prev) => prev.map((n) => (n.id === noteId ? { ...n, historyEnabled: true } : n))) + setEditingNote((prev) => (prev?.note.id === noteId ? { ...prev, note: { ...prev.note, historyEnabled: true } } : prev)) }, []) const handleHistoryRestored = useCallback((restored: Note) => { @@ -411,7 +412,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) { onEdit={(note, readOnly) => setEditingNote({ note, readOnly })} onSizeChange={handleSizeChange} currentNotebookId={searchParams.get('notebook')} - noteHistoryEnabled={noteHistoryEnabled} noteHistoryMode={noteHistoryMode} onOpenHistory={handleOpenHistory} onEnableHistory={handleEnableHistory} @@ -469,8 +469,8 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) { open={historyOpen} onOpenChange={setHistoryOpen} note={historyNote} - enabled={noteHistoryEnabled} - onEnableHistory={handleEnableHistory} + enabled={!!historyNote?.historyEnabled} + onEnableHistory={async () => { if (historyNote) await handleEnableHistory(historyNote.id) }} onRestored={handleHistoryRestored} /> diff --git a/memento-note/components/note-inline-editor.tsx b/memento-note/components/note-inline-editor.tsx index 75de3f6..2470aed 100644 --- a/memento-note/components/note-inline-editor.tsx +++ b/memento-note/components/note-inline-editor.tsx @@ -67,7 +67,7 @@ interface NoteInlineEditorProps { onArchive?: (noteId: string) => void onChange?: (noteId: string, fields: Partial) => void onOpenHistory?: (note: Note) => void - noteHistoryEnabled?: boolean + onEnableHistory?: (noteId: string) => Promise noteHistoryMode?: 'manual' | 'auto' colorKey: NoteColor /** If true and the note is a Markdown note, open directly in preview mode */ @@ -98,7 +98,7 @@ export function NoteInlineEditor({ onArchive, onChange, onOpenHistory, - noteHistoryEnabled = false, + onEnableHistory, noteHistoryMode = 'manual', colorKey, defaultPreviewMode = false, @@ -532,7 +532,7 @@ export function NoteInlineEditor({ {/* Right group: meta actions + save indicator */}
- {noteHistoryEnabled && noteHistoryMode === 'manual' && ( + {note.historyEnabled && noteHistoryMode === 'manual' && (