Files
Momento/memento-note/components/comparison-modal.tsx
Antigravity e2672cd2c2
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped
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>
2026-05-24 14:27:29 +00:00

166 lines
5.8 KiB
TypeScript

'use client'
import { Dialog, DialogContent, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Columns2, GitMerge, X, ExternalLink } from 'lucide-react'
import { cn } from '@/lib/utils'
import { Note } from '@/lib/types'
import { useLanguage } from '@/lib/i18n/LanguageProvider'
interface ComparisonModalProps {
isOpen: boolean
onClose: () => void
notes: Array<Partial<Note>>
similarity?: number
onMergeNotes?: (noteIds: string[]) => void
}
function stripHtml(html: string): string {
return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
}
function openNoteInNewTab(noteId: string) {
window.open(`/home?openNote=${encodeURIComponent(noteId)}`, '_blank', 'noopener,noreferrer')
}
export function ComparisonModal({
isOpen,
onClose,
notes,
similarity,
onMergeNotes,
}: ComparisonModalProps) {
const { t } = useLanguage()
const getNoteColor = (index: number) => {
const colors = [
'border-indigo-200/80 dark:border-indigo-800/80',
'border-purple-200/80 dark:border-purple-800/80',
'border-emerald-200/80 dark:border-emerald-800/80',
]
return colors[index % colors.length]
}
const getTitleColor = (index: number) => {
const colors = [
'text-indigo-700 dark:text-indigo-300',
'text-purple-700 dark:text-purple-300',
'text-emerald-700 dark:text-emerald-300',
]
return colors[index % colors.length]
}
const maxModalWidth = notes.length === 2 ? 'max-w-6xl' : 'max-w-7xl'
const similarityPercentage = similarity ? Math.round(similarity * 100) : 0
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent
showCloseButton={false}
className={cn('max-h-[90vh] overflow-hidden flex flex-col p-0', maxModalWidth)}
>
<div className="flex items-center justify-between p-6 border-b border-border/60">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-indigo-500/10">
<Columns2 className="h-5 w-5 text-indigo-600 dark:text-indigo-400" />
</div>
<div>
<DialogTitle className="text-xl font-semibold">
{t('memoryEcho.comparison.title')}
</DialogTitle>
<DialogDescription className="text-sm text-muted-foreground">
{similarityPercentage > 0
? t('memoryEcho.comparison.similarityInfo', { similarity: similarityPercentage })
: t('memoryEcho.comparison.subtitle')}
</DialogDescription>
</div>
</div>
<button
type="button"
onClick={onClose}
className="p-1 rounded-md text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
aria-label={t('memoryEcho.editorSection.close')}
>
<X className="h-5 w-5" />
</button>
</div>
<div className="px-6 py-3 border-b border-border/60 bg-muted/20">
<p className="text-sm text-muted-foreground">
{t('memoryEcho.comparison.stayOnCurrentNote')}
</p>
</div>
{similarityPercentage >= 80 && (
<div className="px-6 py-3 border-b border-border/60">
<p className="text-sm text-muted-foreground">
{t('memoryEcho.comparison.highSimilarityInsight')}
</p>
</div>
)}
<div
className={cn(
'flex-1 overflow-y-auto p-6',
notes.length === 2 ? 'grid grid-cols-1 md:grid-cols-2 gap-6' : 'grid grid-cols-1 md:grid-cols-3 gap-4'
)}
>
{notes.map((note, index) => {
const title = note.title || t('memoryEcho.comparison.untitled')
const plainContent = stripHtml(note.content || '')
return (
<div
key={note.id || index}
className={cn(
'border rounded-xl p-4 bg-card flex flex-col',
getNoteColor(index)
)}
>
<h3 className={cn('font-semibold text-lg mb-3', getTitleColor(index))}>
{title}
</h3>
<div className="text-sm text-muted-foreground line-clamp-[14] whitespace-pre-wrap flex-1">
{plainContent}
</div>
{note.id && (
<div className="mt-4 pt-3 border-t border-border/60">
<button
type="button"
onClick={() => openNoteInNewTab(note.id!)}
className="text-xs text-muted-foreground hover:text-foreground inline-flex items-center gap-1 transition-colors"
>
<ExternalLink className="h-3 w-3" />
{t('memoryEcho.editorSection.openInEditor')}
</button>
</div>
)}
</div>
)
})}
</div>
<div className="px-6 py-4 border-t border-border/60 bg-muted/30 flex items-center justify-end gap-2">
<Button size="sm" variant="outline" onClick={onClose}>
{t('memoryEcho.editorSection.backToNote')}
</Button>
{onMergeNotes && notes.length >= 2 && (
<Button
size="sm"
className="bg-indigo-600 hover:bg-indigo-700 text-white"
onClick={() => {
const noteIds = notes.map(n => n.id).filter(Boolean) as string[]
onMergeNotes(noteIds)
onClose()
}}
>
<GitMerge className="h-4 w-4 mr-2" />
{t('memoryEcho.editorSection.mergeAll')}
</Button>
)}
</div>
</DialogContent>
</Dialog>
)
}