fix: chat memory lost between messages + per-note history
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m11s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m11s
Chat (AIChat floating widget): conversationId was never captured from the API response, so every message created a new conversation with no context. Now creates the conversation upfront before streaming (same pattern as ChatContainer) so the ID persists across messages. Note history: was stored globally in UserAISettings, so enabling history on one note enabled it for ALL notes. Now each Note has its own historyEnabled boolean field. The "Enable history" action only affects the specific note. A migration adds the column with default false. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -436,14 +436,12 @@ export async function commitNoteHistory(noteId: string) {
|
|||||||
const session = await auth()
|
const session = await auth()
|
||||||
if (!session?.user?.id) throw new Error('Unauthorized')
|
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({
|
const note = await prisma.note.findFirst({
|
||||||
where: { id: noteId, userId: session.user.id },
|
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) throw new Error('Note not found')
|
||||||
|
if (!note.historyEnabled) throw new Error('History is disabled for this note')
|
||||||
|
|
||||||
await createNoteHistorySnapshot({
|
await createNoteHistorySnapshot({
|
||||||
noteId: note.id,
|
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
|
// Search notes - DB-side filtering (fast) with optional semantic search
|
||||||
// Supports contextual search within notebook (IA5)
|
// Supports contextual search within notebook (IA5)
|
||||||
export async function searchNotes(query: string, useSemantic: boolean = false, notebookId?: string) {
|
export async function searchNotes(query: string, useSemantic: boolean = false, notebookId?: string) {
|
||||||
@@ -641,14 +655,9 @@ export async function createNote(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id)
|
// New notes start with historyEnabled=false (schema default),
|
||||||
if (historyEnabled) {
|
// so no initial snapshot is needed here.
|
||||||
await createNoteHistorySnapshot({
|
// History is enabled per-note via enableNoteHistory() action.
|
||||||
noteId: note.id,
|
|
||||||
userId: session.user.id,
|
|
||||||
reason: 'create',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (snapshotError) {
|
} catch (snapshotError) {
|
||||||
console.error('[HISTORY] Failed to create initial snapshot:', snapshotError)
|
console.error('[HISTORY] Failed to create initial snapshot:', snapshotError)
|
||||||
}
|
}
|
||||||
@@ -783,7 +792,7 @@ export async function updateNote(id: string, data: {
|
|||||||
try {
|
try {
|
||||||
const oldNote = await prisma.note.findUnique({
|
const oldNote = await prisma.note.findUnique({
|
||||||
where: { id, userId: session.user.id },
|
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 oldLabels: string[] = oldNote?.labels ? JSON.parse(oldNote.labels) : []
|
||||||
const oldNotebookId = oldNote?.notebookId
|
const oldNotebookId = oldNote?.notebookId
|
||||||
@@ -854,7 +863,7 @@ export async function updateNote(id: string, data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id)
|
const historyEnabled = oldNote?.historyEnabled === true
|
||||||
if (historyEnabled && shouldCaptureHistorySnapshot(data as Record<string, unknown>)) {
|
if (historyEnabled && shouldCaptureHistorySnapshot(data as Record<string, unknown>)) {
|
||||||
const mode = await getNoteHistoryMode(session.user.id)
|
const mode = await getNoteHistoryMode(session.user.id)
|
||||||
if (mode === 'manual') {
|
if (mode === 'manual') {
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ export async function PUT(
|
|||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const historyEnabled = await isNoteHistoryEnabledForUser(session.user.id)
|
const historyEnabled = existingNote.historyEnabled === true
|
||||||
if (historyEnabled && shouldCaptureHistorySnapshot(updateData)) {
|
if (historyEnabled && shouldCaptureHistorySnapshot(updateData)) {
|
||||||
const mode = await getNoteHistoryMode(session.user.id)
|
const mode = await getNoteHistoryMode(session.user.id)
|
||||||
if (mode === 'auto') {
|
if (mode === 'auto') {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { useWebSearchAvailable } from '@/hooks/use-web-search-available'
|
|||||||
import { useNotebooks } from '@/context/notebooks-context'
|
import { useNotebooks } from '@/context/notebooks-context'
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
import { createConversation } from '@/app/actions/chat-actions'
|
||||||
|
|
||||||
function getTextContent(msg: UIMessage): string {
|
function getTextContent(msg: UIMessage): string {
|
||||||
if (msg.parts && Array.isArray(msg.parts)) {
|
if (msg.parts && Array.isArray(msg.parts)) {
|
||||||
@@ -68,6 +69,20 @@ export function AIChat() {
|
|||||||
const text = input.trim()
|
const text = input.trim()
|
||||||
if (!text || isLoading) return
|
if (!text || isLoading) return
|
||||||
setInput('')
|
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(
|
await sendMessage(
|
||||||
{ text },
|
{ text },
|
||||||
{
|
{
|
||||||
@@ -76,7 +91,7 @@ export function AIChat() {
|
|||||||
chatScope,
|
chatScope,
|
||||||
notebookId: chatScope !== 'all' ? chatScope : undefined,
|
notebookId: chatScope !== 'all' ? chatScope : undefined,
|
||||||
webSearch: webSearch && webSearchAvailable,
|
webSearch: webSearch && webSearchAvailable,
|
||||||
conversationId,
|
conversationId: convId,
|
||||||
language,
|
language,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ export function ChatContainer({ initialConversations, notebooks, webSearchAvaila
|
|||||||
|
|
||||||
const isLoading = status === 'submitted' || status === 'streaming'
|
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
|
// Timeout warning: show toast if response takes > 30s
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading) return
|
if (!isLoading) return
|
||||||
@@ -105,13 +112,6 @@ export function ChatContainer({ initialConversations, notebooks, webSearchAvaila
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [currentId])
|
}, [currentId])
|
||||||
|
|
||||||
const refreshConversations = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const updated = await getConversations()
|
|
||||||
setConversations(updated)
|
|
||||||
} catch {}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const handleSendMessage = async (content: string, notebookId?: string) => {
|
const handleSendMessage = async (content: string, notebookId?: string) => {
|
||||||
if (notebookId) {
|
if (notebookId) {
|
||||||
setSelectedNotebook(notebookId)
|
setSelectedNotebook(notebookId)
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'
|
|||||||
import { useSearchParams, useRouter } from 'next/navigation'
|
import { useSearchParams, useRouter } from 'next/navigation'
|
||||||
import dynamic from 'next/dynamic'
|
import dynamic from 'next/dynamic'
|
||||||
import { Note } from '@/lib/types'
|
import { Note } from '@/lib/types'
|
||||||
import { updateAISettings } from '@/app/actions/ai-settings'
|
import { getAllNotes, searchNotes, enableNoteHistory } from '@/app/actions/notes'
|
||||||
import { getAllNotes, searchNotes } from '@/app/actions/notes'
|
|
||||||
import { NoteInput } from '@/components/note-input'
|
import { NoteInput } from '@/components/note-input'
|
||||||
import { NotesMainSection, type NotesViewMode } from '@/components/notes-main-section'
|
import { NotesMainSection, type NotesViewMode } from '@/components/notes-main-section'
|
||||||
import { NotesViewToggle } from '@/components/notes-view-toggle'
|
import { NotesViewToggle } from '@/components/notes-view-toggle'
|
||||||
@@ -62,7 +61,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
initialNotes.filter(n => n.isPinned)
|
initialNotes.filter(n => n.isPinned)
|
||||||
)
|
)
|
||||||
const [notesViewMode, setNotesViewMode] = useState<NotesViewMode>(initialSettings.notesViewMode)
|
const [notesViewMode, setNotesViewMode] = useState<NotesViewMode>(initialSettings.notesViewMode)
|
||||||
const [noteHistoryEnabled, setNoteHistoryEnabled] = useState(initialSettings.noteHistory)
|
|
||||||
const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode)
|
const [noteHistoryMode] = useState<'manual' | 'auto'>(initialSettings.noteHistoryMode)
|
||||||
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null)
|
const [editingNote, setEditingNote] = useState<{ note: Note; readOnly?: boolean } | null>(null)
|
||||||
const [isLoading, setIsLoading] = useState(false) // false by default — data is pre-loaded
|
const [isLoading, setIsLoading] = useState(false) // false by default — data is pre-loaded
|
||||||
@@ -145,9 +143,12 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
setHistoryOpen(true)
|
setHistoryOpen(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const handleEnableHistory = useCallback(async () => {
|
const handleEnableHistory = useCallback(async (noteId: string) => {
|
||||||
await updateAISettings({ noteHistory: true })
|
await enableNoteHistory(noteId)
|
||||||
setNoteHistoryEnabled(true)
|
// 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) => {
|
const handleHistoryRestored = useCallback((restored: Note) => {
|
||||||
@@ -411,7 +412,6 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
|
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
|
||||||
onSizeChange={handleSizeChange}
|
onSizeChange={handleSizeChange}
|
||||||
currentNotebookId={searchParams.get('notebook')}
|
currentNotebookId={searchParams.get('notebook')}
|
||||||
noteHistoryEnabled={noteHistoryEnabled}
|
|
||||||
noteHistoryMode={noteHistoryMode}
|
noteHistoryMode={noteHistoryMode}
|
||||||
onOpenHistory={handleOpenHistory}
|
onOpenHistory={handleOpenHistory}
|
||||||
onEnableHistory={handleEnableHistory}
|
onEnableHistory={handleEnableHistory}
|
||||||
@@ -469,8 +469,8 @@ export function HomeClient({ initialNotes, initialSettings }: HomeClientProps) {
|
|||||||
open={historyOpen}
|
open={historyOpen}
|
||||||
onOpenChange={setHistoryOpen}
|
onOpenChange={setHistoryOpen}
|
||||||
note={historyNote}
|
note={historyNote}
|
||||||
enabled={noteHistoryEnabled}
|
enabled={!!historyNote?.historyEnabled}
|
||||||
onEnableHistory={handleEnableHistory}
|
onEnableHistory={async () => { if (historyNote) await handleEnableHistory(historyNote.id) }}
|
||||||
onRestored={handleHistoryRestored}
|
onRestored={handleHistoryRestored}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ interface NoteInlineEditorProps {
|
|||||||
onArchive?: (noteId: string) => void
|
onArchive?: (noteId: string) => void
|
||||||
onChange?: (noteId: string, fields: Partial<Note>) => void
|
onChange?: (noteId: string, fields: Partial<Note>) => void
|
||||||
onOpenHistory?: (note: Note) => void
|
onOpenHistory?: (note: Note) => void
|
||||||
noteHistoryEnabled?: boolean
|
onEnableHistory?: (noteId: string) => Promise<void>
|
||||||
noteHistoryMode?: 'manual' | 'auto'
|
noteHistoryMode?: 'manual' | 'auto'
|
||||||
colorKey: NoteColor
|
colorKey: NoteColor
|
||||||
/** If true and the note is a Markdown note, open directly in preview mode */
|
/** If true and the note is a Markdown note, open directly in preview mode */
|
||||||
@@ -98,7 +98,7 @@ export function NoteInlineEditor({
|
|||||||
onArchive,
|
onArchive,
|
||||||
onChange,
|
onChange,
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
noteHistoryEnabled = false,
|
onEnableHistory,
|
||||||
noteHistoryMode = 'manual',
|
noteHistoryMode = 'manual',
|
||||||
colorKey,
|
colorKey,
|
||||||
defaultPreviewMode = false,
|
defaultPreviewMode = false,
|
||||||
@@ -532,7 +532,7 @@ export function NoteInlineEditor({
|
|||||||
|
|
||||||
{/* Right group: meta actions + save indicator */}
|
{/* Right group: meta actions + save indicator */}
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{noteHistoryEnabled && noteHistoryMode === 'manual' && (
|
{note.historyEnabled && noteHistoryMode === 'manual' && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -596,10 +596,16 @@ export function NoteInlineEditor({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{onOpenHistory && (
|
{onOpenHistory && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => onOpenHistory(note)}
|
onClick={() => {
|
||||||
|
if (note.historyEnabled) {
|
||||||
|
onOpenHistory(note)
|
||||||
|
} else if (onEnableHistory) {
|
||||||
|
onEnableHistory(note.id).then(() => onOpenHistory(note))
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<History className="h-4 w-4 mr-2" />
|
<History className="h-4 w-4 mr-2" />
|
||||||
{noteHistoryEnabled
|
{note.historyEnabled
|
||||||
? (t('notes.history') || 'Historique')
|
? (t('notes.history') || 'Historique')
|
||||||
: (t('notes.enableHistory') || "Activer l'historique")}
|
: (t('notes.enableHistory') || "Activer l'historique")}
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ interface NotesMainSectionProps {
|
|||||||
onEdit?: (note: Note, readOnly?: boolean) => void
|
onEdit?: (note: Note, readOnly?: boolean) => void
|
||||||
onSizeChange?: (noteId: string, size: 'small' | 'medium' | 'large') => void
|
onSizeChange?: (noteId: string, size: 'small' | 'medium' | 'large') => void
|
||||||
currentNotebookId?: string | null
|
currentNotebookId?: string | null
|
||||||
noteHistoryEnabled?: boolean
|
|
||||||
noteHistoryMode?: 'manual' | 'auto'
|
noteHistoryMode?: 'manual' | 'auto'
|
||||||
onOpenHistory?: (note: Note) => void
|
onOpenHistory?: (note: Note) => void
|
||||||
onEnableHistory?: () => Promise<void>
|
onEnableHistory?: (noteId: string) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function NotesMainSection({
|
export function NotesMainSection({
|
||||||
@@ -37,7 +36,6 @@ export function NotesMainSection({
|
|||||||
onEdit,
|
onEdit,
|
||||||
onSizeChange,
|
onSizeChange,
|
||||||
currentNotebookId,
|
currentNotebookId,
|
||||||
noteHistoryEnabled = false,
|
|
||||||
noteHistoryMode = 'manual',
|
noteHistoryMode = 'manual',
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
onEnableHistory,
|
onEnableHistory,
|
||||||
@@ -49,7 +47,6 @@ export function NotesMainSection({
|
|||||||
notes={notes}
|
notes={notes}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
currentNotebookId={currentNotebookId}
|
currentNotebookId={currentNotebookId}
|
||||||
noteHistoryEnabled={noteHistoryEnabled}
|
|
||||||
noteHistoryMode={noteHistoryMode}
|
noteHistoryMode={noteHistoryMode}
|
||||||
onOpenHistory={onOpenHistory}
|
onOpenHistory={onOpenHistory}
|
||||||
onEnableHistory={onEnableHistory}
|
onEnableHistory={onEnableHistory}
|
||||||
@@ -64,7 +61,6 @@ export function NotesMainSection({
|
|||||||
notes={notes}
|
notes={notes}
|
||||||
onEdit={onEdit}
|
onEdit={onEdit}
|
||||||
onSizeChange={onSizeChange}
|
onSizeChange={onSizeChange}
|
||||||
noteHistoryEnabled={noteHistoryEnabled}
|
|
||||||
noteHistoryMode={noteHistoryMode}
|
noteHistoryMode={noteHistoryMode}
|
||||||
onOpenHistory={onOpenHistory}
|
onOpenHistory={onOpenHistory}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -83,10 +83,9 @@ interface NotesTabsViewProps {
|
|||||||
notes: Note[]
|
notes: Note[]
|
||||||
onEdit?: (note: Note, readOnly?: boolean) => void
|
onEdit?: (note: Note, readOnly?: boolean) => void
|
||||||
currentNotebookId?: string | null
|
currentNotebookId?: string | null
|
||||||
noteHistoryEnabled?: boolean
|
|
||||||
noteHistoryMode?: 'manual' | 'auto'
|
noteHistoryMode?: 'manual' | 'auto'
|
||||||
onOpenHistory?: (note: Note) => void
|
onOpenHistory?: (note: Note) => void
|
||||||
onEnableHistory?: () => Promise<void>
|
onEnableHistory?: (noteId: string) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
type SortOrder = 'date-desc' | 'date-asc' | 'title-asc' | 'title-desc'
|
type SortOrder = 'date-desc' | 'date-asc' | 'title-asc' | 'title-desc'
|
||||||
@@ -382,16 +381,14 @@ function NoteMetaSidebar({
|
|||||||
note,
|
note,
|
||||||
onPinToggle,
|
onPinToggle,
|
||||||
onArchive,
|
onArchive,
|
||||||
noteHistoryEnabled = false,
|
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
onEnableHistory,
|
onEnableHistory,
|
||||||
}: {
|
}: {
|
||||||
note: Note
|
note: Note
|
||||||
onPinToggle: (note: Note) => void
|
onPinToggle: (note: Note) => void
|
||||||
onArchive: (note: Note) => void
|
onArchive: (note: Note) => void
|
||||||
noteHistoryEnabled?: boolean
|
|
||||||
onOpenHistory?: (note: Note) => void
|
onOpenHistory?: (note: Note) => void
|
||||||
onEnableHistory?: () => Promise<void>
|
onEnableHistory?: (noteId: string) => Promise<void>
|
||||||
}) {
|
}) {
|
||||||
const { t } = useLanguage()
|
const { t } = useLanguage()
|
||||||
const { notebooks, moveNoteToNotebookOptimistic } = useNotebooks()
|
const { notebooks, moveNoteToNotebookOptimistic } = useNotebooks()
|
||||||
@@ -427,13 +424,13 @@ function NoteMetaSidebar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleHistory = async () => {
|
const handleHistory = async () => {
|
||||||
if (!noteHistoryEnabled) {
|
if (!note.historyEnabled) {
|
||||||
if (!onEnableHistory) {
|
if (!onEnableHistory) {
|
||||||
toast.info(ts('notes.historyDisabledDesc', "L'historique est désactivé pour votre compte."))
|
toast.info(ts('notes.historyDisabledDesc', "L'historique est désactivé pour cette note."))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await onEnableHistory()
|
await onEnableHistory(note.id)
|
||||||
toast.success(ts('notes.historyEnabled', 'Historique activé'))
|
toast.success(ts('notes.historyEnabled', 'Historique activé'))
|
||||||
onOpenHistory?.(note)
|
onOpenHistory?.(note)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -577,7 +574,7 @@ function NoteMetaSidebar({
|
|||||||
<SidebarActionBtn
|
<SidebarActionBtn
|
||||||
icon={<History className="h-3.5 w-3.5" />}
|
icon={<History className="h-3.5 w-3.5" />}
|
||||||
label={
|
label={
|
||||||
noteHistoryEnabled
|
note.historyEnabled
|
||||||
? ts('notes.history', 'Historique')
|
? ts('notes.history', 'Historique')
|
||||||
: ts('notes.enableHistory', "Activer l'historique")
|
: ts('notes.enableHistory', "Activer l'historique")
|
||||||
}
|
}
|
||||||
@@ -596,7 +593,7 @@ export function NotesTabsView({
|
|||||||
notes,
|
notes,
|
||||||
onEdit,
|
onEdit,
|
||||||
currentNotebookId,
|
currentNotebookId,
|
||||||
noteHistoryEnabled = false,
|
|
||||||
noteHistoryMode = 'manual',
|
noteHistoryMode = 'manual',
|
||||||
onOpenHistory,
|
onOpenHistory,
|
||||||
onEnableHistory,
|
onEnableHistory,
|
||||||
@@ -893,9 +890,9 @@ export function NotesTabsView({
|
|||||||
<NoteInlineEditor
|
<NoteInlineEditor
|
||||||
key={selected.id}
|
key={selected.id}
|
||||||
note={selected}
|
note={selected}
|
||||||
noteHistoryEnabled={noteHistoryEnabled}
|
|
||||||
noteHistoryMode={noteHistoryMode}
|
noteHistoryMode={noteHistoryMode}
|
||||||
onOpenHistory={onOpenHistory}
|
onOpenHistory={onOpenHistory}
|
||||||
|
onEnableHistory={onEnableHistory}
|
||||||
colorKey={colorKey}
|
colorKey={colorKey}
|
||||||
defaultPreviewMode={true}
|
defaultPreviewMode={true}
|
||||||
onChange={(noteId, fields) => {
|
onChange={(noteId, fields) => {
|
||||||
@@ -932,7 +929,6 @@ export function NotesTabsView({
|
|||||||
note={selected}
|
note={selected}
|
||||||
onPinToggle={handlePinToggle}
|
onPinToggle={handlePinToggle}
|
||||||
onArchive={handleArchive}
|
onArchive={handleArchive}
|
||||||
noteHistoryEnabled={noteHistoryEnabled}
|
|
||||||
onOpenHistory={onOpenHistory}
|
onOpenHistory={onOpenHistory}
|
||||||
onEnableHistory={onEnableHistory}
|
onEnableHistory={onEnableHistory}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export interface Note {
|
|||||||
notebook?: Notebook | null;
|
notebook?: Notebook | null;
|
||||||
autoGenerated?: boolean | null;
|
autoGenerated?: boolean | null;
|
||||||
aiProvider?: string | null;
|
aiProvider?: string | null;
|
||||||
|
historyEnabled?: boolean;
|
||||||
// Search result metadata (optional)
|
// Search result metadata (optional)
|
||||||
matchType?: 'exact' | 'related' | null;
|
matchType?: 'exact' | 'related' | null;
|
||||||
searchScore?: number | null;
|
searchScore?: number | null;
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Note" ADD COLUMN "historyEnabled" BOOLEAN NOT NULL DEFAULT false;
|
||||||
@@ -154,6 +154,7 @@ model Note {
|
|||||||
shares NoteShare[]
|
shares NoteShare[]
|
||||||
labelRelations Label[] @relation("LabelToNote")
|
labelRelations Label[] @relation("LabelToNote")
|
||||||
historyEntries NoteHistory[]
|
historyEntries NoteHistory[]
|
||||||
|
historyEnabled Boolean @default(false)
|
||||||
|
|
||||||
@@index([isPinned])
|
@@index([isPinned])
|
||||||
@@index([isArchived])
|
@@index([isArchived])
|
||||||
|
|||||||
Reference in New Issue
Block a user