feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,20 +21,40 @@ import { LabelBadge } from '@/components/label-badge'
|
||||
import { NoteAttachments } from '@/components/note-attachments'
|
||||
import { DocumentQAOverlay } from '@/components/document-qa-overlay'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { useAiConsent } from '@/components/legal/ai-consent-provider'
|
||||
import { useState } from 'react'
|
||||
import { WikilinksBacklinksPanel } from '@/components/wikilinks-backlinks-panel'
|
||||
import { MemoryEchoSection } from '@/components/memory-echo-section'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
interface NoteEditorFullPageProps {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
const router = useRouter()
|
||||
const { t, language } = useLanguage()
|
||||
const { requestAiConsent } = useAiConsent()
|
||||
const dateLocale = language === 'fr' ? fr : enUS
|
||||
const { state, actions, note, readOnly, notebooks, fileInputRef, globalLabels } = useNoteEditorContext()
|
||||
const [docQAAttachment, setDocQAAttachment] = useState<{ id: string; fileName: string } | null>(null)
|
||||
const [attachmentsCount, setAttachmentsCount] = useState(0)
|
||||
const [uploadTrigger, setUploadTrigger] = useState(0)
|
||||
const [comparisonSimilarity, setComparisonSimilarity] = useState<number | undefined>()
|
||||
|
||||
const fetchNotesByIds = async (noteIds: string[]) => {
|
||||
const notes = await Promise.all(noteIds.map(async (id) => {
|
||||
try {
|
||||
const res = await fetch(`/api/notes/${id}`)
|
||||
if (!res.ok) return null
|
||||
const data = await res.json()
|
||||
return data.success && data.data ? data.data as Partial<Note> : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}))
|
||||
return notes.filter((n): n is Partial<Note> => n !== null)
|
||||
}
|
||||
|
||||
const notebookName = notebooks.find(nb => nb.id === note.notebookId)?.name || null
|
||||
|
||||
@@ -109,6 +129,19 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
<div className="max-w-3xl mx-auto w-full space-y-8 pb-32">
|
||||
<NoteContentArea />
|
||||
|
||||
{!readOnly && (
|
||||
<MemoryEchoSection
|
||||
noteId={note.id}
|
||||
onCompareNotes={async (noteIds, meta) => {
|
||||
setComparisonSimilarity(meta?.similarity)
|
||||
actions.setComparisonNotes(await fetchNotesByIds(noteIds))
|
||||
}}
|
||||
onMergeNotes={async (noteIds) => {
|
||||
actions.setFusionNotes(await fetchNotesByIds(noteIds))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NoteAttachments
|
||||
noteId={note.id}
|
||||
onOpenDocQA={(att) => setDocQAAttachment(att)}
|
||||
@@ -149,6 +182,8 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
toast.error(t('ai.titleGenerationMinWords', { count: wordCount }))
|
||||
return
|
||||
}
|
||||
const consented = await requestAiConsent()
|
||||
if (!consented) return
|
||||
actions.setIsProcessingAI(true)
|
||||
try {
|
||||
const res = await fetch('/api/ai/title-suggestions', {
|
||||
@@ -209,6 +244,55 @@ export function NoteEditorFullPage({ onClose }: NoteEditorFullPageProps) {
|
||||
onSave={actions.handleReminderSave}
|
||||
onRemove={actions.handleRemoveReminder}
|
||||
/>
|
||||
|
||||
{state.comparisonNotes.length > 0 && (
|
||||
<ComparisonModal
|
||||
isOpen
|
||||
onClose={() => {
|
||||
setComparisonSimilarity(undefined)
|
||||
actions.setComparisonNotes([])
|
||||
}}
|
||||
notes={state.comparisonNotes}
|
||||
similarity={comparisonSimilarity}
|
||||
onMergeNotes={async (noteIds) => {
|
||||
actions.setFusionNotes(await fetchNotesByIds(noteIds))
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{state.fusionNotes.length > 0 && (
|
||||
<FusionModal
|
||||
isOpen
|
||||
onClose={() => actions.setFusionNotes([])}
|
||||
notes={state.fusionNotes}
|
||||
onConfirmFusion={async ({ title, content }, options) => {
|
||||
await actions.handleSaveInPlace()
|
||||
const { createNote, updateNote } = await import('@/app/actions/notes')
|
||||
await createNote({
|
||||
title,
|
||||
content,
|
||||
labels: options.keepAllTags
|
||||
? [...new Set(state.fusionNotes.flatMap(n => n.labels || []))]
|
||||
: state.fusionNotes[0].labels || [],
|
||||
color: state.fusionNotes[0].color,
|
||||
type: 'text',
|
||||
isMarkdown: true,
|
||||
autoGenerated: true,
|
||||
aiProvider: 'fusion',
|
||||
notebookId: state.fusionNotes[0].notebookId ?? undefined,
|
||||
})
|
||||
if (options.archiveOriginals) {
|
||||
for (const fusionNote of state.fusionNotes) {
|
||||
if (fusionNote.id) {
|
||||
await updateNote(fusionNote.id, { isArchived: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
toast.success(t('toast.notesFusionSuccess'))
|
||||
actions.setFusionNotes([])
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user