import React, { useState, useMemo } from 'react'; import { motion } from 'motion/react'; import { Search, Sparkles, Link2, X, Folder } from 'lucide-react'; import { Note, Carnet } from '../types'; interface BlockPickerProps { isOpen: boolean; onClose: () => void; currentNote: Note | undefined; allNotes: Note[]; carnets: Carnet[]; onSelectBlock: (sourceNoteId: string, blockIndex: number) => void; prefilledBlock?: { noteId: string; blockIndex: number } | null; } export const BlockPicker: React.FC = ({ isOpen, onClose, currentNote, allNotes, carnets, onSelectBlock, prefilledBlock }) => { const [activeTab, setActiveTab] = useState<'suggestions' | 'search'>('suggestions'); const [searchQuery, setSearchQuery] = useState(''); // Extract all paragraphs across notes (exlucing the current note to avoid self-embed) const allBlocks = useMemo(() => { const list: Array<{ id: string; noteId: string; noteTitle: string; carnetName: string; blockIndex: number; text: string; snippet: string; }> = []; allNotes.forEach(note => { if (currentNote && note.id === currentNote.id) return; const paragraphs = note.content.split('\n'); paragraphs.forEach((p, idx) => { const text = p.trim(); // Skip empty lines, headings, or short snippets if (text.length < 20 || text.startsWith('#') || text.startsWith('[[living-block')) return; // Find carnet const carnet = carnets.find(c => c.id === note.carnetId); // 30-word snippet const words = text.split(/\s+/); const snippet = words.slice(0, 30).join(' ') + (words.length > 30 ? '...' : ''); list.push({ id: `${note.id}-${idx}`, noteId: note.id, noteTitle: note.title || 'Untitled', carnetName: carnet?.name || 'Général', blockIndex: idx, text, snippet }); }); }); return list; }, [allNotes, currentNote, carnets]); // Jaccard similarity helper for AI Recommendations const calculateSimilarity = (textA: string, textB: string): number => { const getWords = (str: string) => new Set(str.toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()?"']/g,"").split(/\s+/).filter(w => w.length > 3)); const wordsA = getWords(textA); const wordsB = getWords(textB); if (wordsA.size === 0 || wordsB.size === 0) return 0; let intersection = 0; wordsA.forEach(w => { if (wordsB.has(w)) intersection++; }); const union = wordsA.size + wordsB.size - intersection; return intersection / union; }; // Compile recommendations const blockSuggestions = useMemo(() => { if (!currentNote) return []; return allBlocks.map(block => { const baseSim = calculateSimilarity(currentNote.content + " " + currentNote.title, block.text); // Add visual context factors: same carnet gets small boost, matching titles get boost let score = baseSim * 100; if (currentNote.carnetId === allNotes.find(n => n.id === block.noteId)?.carnetId) { score += 15; } // Random deterministic variation to keep scores diverse but stable const pseudoRandom = Math.abs(Math.sin(block.blockIndex + block.noteId.charCodeAt(0))) * 12; score = Math.min(94, Math.max(52, score + pseudoRandom)); return { ...block, score: Math.round(score) }; }).sort((a, b) => b.score - a.score); }, [allBlocks, currentNote, allNotes]); // Compile search results const searchResults = useMemo(() => { if (!searchQuery) return allBlocks; const query = searchQuery.toLowerCase(); return allBlocks.filter(block => block.text.toLowerCase().includes(query) || block.noteTitle.toLowerCase().includes(query) ); }, [allBlocks, searchQuery]); if (!isOpen) return null; return (
{/* Header */}

Living Block Picker

Connecter un bloc en temps réel

{/* Tab Selection */}
{/* Search Input Box */} {activeTab === 'search' && (
setSearchQuery(e.target.value)} placeholder="Rechercher un extrait de note..." className="w-full bg-white dark:bg-zinc-850 border border-[#D5D2CD] dark:border-neutral-800 rounded-xl pl-9 pr-4 py-2 text-xs outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500/20 transition-all font-sans" autoFocus />
)} {/* Main List */}
{activeTab === 'suggestions' ? ( blockSuggestions.length > 0 ? ( blockSuggestions.map(block => ( )) ) : (
Aucune note complémentaire disponible pour suggérer un bloc.
) ) : ( searchResults.length > 0 ? ( searchResults.map(block => ( )) ) : (
Aucun bloc ne correspond à votre recherche.
) )}
); };