Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NeoNotes - Advanced Note Taking with Mind Mapping</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cytoscape.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/cytoscape-cose-bilkent.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow/[email protected]/dist/tf.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/[email protected]/dist/universal-sentence-encoder.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| #cy { | |
| width: 100%; | |
| height: 500px; | |
| border: 1px solid #e5e7eb; | |
| border-radius: 0.5rem; | |
| background-color: #f9fafb; | |
| } | |
| .note-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| } | |
| .tag-chip { | |
| transition: all 0.2s ease; | |
| } | |
| .tag-chip:hover { | |
| background-color: #3b82f6; | |
| color: white; | |
| } | |
| .mindmap-node { | |
| transition: all 0.3s ease; | |
| } | |
| .mindmap-node:hover { | |
| transform: scale(1.05); | |
| } | |
| .ql-editor { | |
| min-height: 200px; | |
| font-size: 16px; | |
| line-height: 1.6; | |
| } | |
| .search-highlight { | |
| background-color: rgba(255, 255, 0, 0.5); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="flex h-screen overflow-hidden"> | |
| <!-- Sidebar --> | |
| <div class="w-64 bg-white border-r border-gray-200 flex flex-col"> | |
| <div class="p-4 border-b border-gray-200"> | |
| <h1 class="text-2xl font-bold text-blue-600 flex items-center"> | |
| <i class="fas fa-project-diagram mr-2"></i> NeoNotes | |
| </h1> | |
| <p class="text-sm text-gray-500">Advanced note-taking with mind mapping</p> | |
| </div> | |
| <div class="p-4"> | |
| <button id="new-note-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center mb-4"> | |
| <i class="fas fa-plus mr-2"></i> New Note | |
| </button> | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-gray-700 mb-2">Quick Filters</h3> | |
| <div class="space-y-1"> | |
| <button class="filter-btn w-full text-left px-2 py-1 rounded hover:bg-gray-100" data-filter="recent"> | |
| <i class="fas fa-clock mr-2 text-gray-500"></i> Recent | |
| </button> | |
| <button class="filter-btn w-full text-left px-2 py-1 rounded hover:bg-gray-100" data-filter="starred"> | |
| <i class="fas fa-star mr-2 text-yellow-500"></i> Starred | |
| </button> | |
| <button class="filter-btn w-full text-left px-2 py-1 rounded hover:bg-gray-100" data-filter="connected"> | |
| <i class="fas fa-link mr-2 text-blue-500"></i> Highly Connected | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-gray-700 mb-2">Tags</h3> | |
| <div id="tag-cloud" class="flex flex-wrap gap-2"> | |
| <!-- Tags will be dynamically added here --> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <h3 class="font-semibold text-gray-700 mb-2">Mind Map Views</h3> | |
| <div class="space-y-1"> | |
| <button class="map-view-btn w-full text-left px-2 py-1 rounded hover:bg-gray-100" data-view="full"> | |
| <i class="fas fa-globe mr-2 text-green-500"></i> Full Knowledge Graph | |
| </button> | |
| <button class="map-view-btn w-full text-left px-2 py-1 rounded hover:bg-gray-100" data-view="current"> | |
| <i class="fas fa-focus mr-2 text-purple-500"></i> Current Note Context | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-auto p-4 border-t border-gray-200"> | |
| <div class="flex items-center"> | |
| <div class="w-8 h-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-600"> | |
| <i class="fas fa-user"></i> | |
| </div> | |
| <div class="ml-2"> | |
| <p class="text-sm font-medium">User</p> | |
| <p class="text-xs text-gray-500">Premium</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <!-- Top Bar --> | |
| <div class="bg-white border-b border-gray-200 p-4 flex items-center justify-between"> | |
| <div class="relative w-1/3"> | |
| <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> | |
| <i class="fas fa-search text-gray-400"></i> | |
| </div> | |
| <input id="search-input" type="text" class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm" placeholder="Search notes, tags, or connections..."> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="ai-suggest-btn" class="flex items-center text-sm text-gray-600 hover:text-blue-600"> | |
| <i class="fas fa-lightbulb mr-1"></i> AI Suggestions | |
| </button> | |
| <button id="embedding-btn" class="flex items-center text-sm text-gray-600 hover:text-blue-600"> | |
| <i class="fas fa-brain mr-1"></i> Analyze Text | |
| </button> | |
| <button id="visualize-btn" class="flex items-center text-sm text-gray-600 hover:text-blue-600"> | |
| <i class="fas fa-project-diagram mr-1"></i> Visualize | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Dual Panel Layout --> | |
| <div class="flex-1 flex overflow-hidden"> | |
| <!-- Notes List Panel --> | |
| <div class="w-1/3 border-r border-gray-200 bg-white overflow-y-auto"> | |
| <div class="p-4"> | |
| <h2 class="text-lg font-semibold text-gray-800 mb-4">Your Notes</h2> | |
| <div id="notes-list" class="space-y-3"> | |
| <!-- Notes will be dynamically added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Note Editor and Visualization Panel --> | |
| <div class="flex-1 flex flex-col overflow-hidden"> | |
| <div class="flex-1 overflow-y-auto p-6"> | |
| <!-- Note Editor View --> | |
| <div id="editor-view" class="h-full"> | |
| <div class="mb-4 flex justify-between items-center"> | |
| <input id="note-title" type="text" class="text-2xl font-bold w-full border-none focus:ring-0 focus:outline-none" placeholder="Note Title"> | |
| <div class="flex space-x-2"> | |
| <button id="star-note" class="text-gray-400 hover:text-yellow-500"> | |
| <i class="far fa-star"></i> | |
| </button> | |
| <button id="delete-note" class="text-gray-400 hover:text-red-500"> | |
| <i class="far fa-trash-alt"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <div id="tags-input" class="flex flex-wrap items-center gap-2"> | |
| <!-- Tags will be dynamically added here --> | |
| <input id="new-tag-input" type="text" class="flex-1 min-w-0 border-none focus:ring-0 focus:outline-none text-sm" placeholder="Add tags..."> | |
| </div> | |
| </div> | |
| <div id="editor-container" class="bg-white rounded-lg border border-gray-200"> | |
| <div id="editor" class="p-4"></div> | |
| </div> | |
| <div class="mt-4"> | |
| <h3 class="font-medium text-gray-700 mb-2">Connections</h3> | |
| <div id="connections-list" class="flex flex-wrap gap-2"> | |
| <!-- Connections will be dynamically added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Visualization View --> | |
| <div id="visualization-view" class="h-full hidden"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-semibold">Knowledge Graph Visualization</h2> | |
| <div class="flex space-x-2"> | |
| <button id="close-visualization" class="text-gray-600 hover:text-blue-600"> | |
| <i class="fas fa-times"></i> Close | |
| </button> | |
| </div> | |
| </div> | |
| <div id="cy"></div> | |
| <div class="mt-4 flex justify-between"> | |
| <div> | |
| <span class="inline-block w-3 h-3 rounded-full bg-blue-500 mr-1"></span> | |
| <span class="text-sm">Current Note</span> | |
| <span class="inline-block w-3 h-3 rounded-full bg-green-500 mr-1 ml-2"></span> | |
| <span class="text-sm">Connected Notes</span> | |
| <span class="inline-block w-3 h-3 rounded-full bg-gray-300 mr-1 ml-2"></span> | |
| <span class="text-sm">Other Notes</span> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="layout-force" class="px-3 py-1 text-sm bg-gray-100 rounded hover:bg-gray-200"> | |
| Force Layout | |
| </button> | |
| <button id="layout-hierarchical" class="px-3 py-1 text-sm bg-gray-100 rounded hover:bg-gray-200"> | |
| Hierarchical | |
| </button> | |
| <button id="layout-concentric" class="px-3 py-1 text-sm bg-gray-100 rounded hover:bg-gray-200"> | |
| Concentric | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- AI Suggestions Panel --> | |
| <div id="ai-panel" class="hidden border-t border-gray-200 bg-gray-50 p-4"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="font-medium">AI Suggestions</h3> | |
| <button id="close-ai" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="ai-suggestions" class="space-y-3"> | |
| <div class="p-3 bg-white rounded-lg border border-gray-200"> | |
| <p class="font-medium text-blue-600 mb-1">Related Concepts</p> | |
| <div class="flex flex-wrap gap-2"> | |
| <span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">Machine Learning</span> | |
| <span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">Neural Networks</span> | |
| <span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full">Deep Learning</span> | |
| </div> | |
| </div> | |
| <div class="p-3 bg-white rounded-lg border border-gray-200"> | |
| <p class="font-medium text-purple-600 mb-1">Potential Connections</p> | |
| <ul class="list-disc list-inside text-sm space-y-1"> | |
| <li>Link to your note on "Introduction to AI"</li> | |
| <li>Connect with "History of Neural Networks"</li> | |
| <li>Reference your research on "Backpropagation"</li> | |
| </ul> | |
| </div> | |
| <div class="p-3 bg-white rounded-lg border border-gray-200"> | |
| <p class="font-medium text-green-600 mb-1">Content Suggestions</p> | |
| <p class="text-sm">Consider adding a section on different types of neural network architectures (CNNs, RNNs, Transformers) and their applications.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Quill Editor --> | |
| <script src="https://cdn.quilljs.com/1.3.6/quill.js"></script> | |
| <link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"> | |
| <script> | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize Quill editor | |
| const quill = new Quill('#editor', { | |
| theme: 'snow', | |
| modules: { | |
| toolbar: [ | |
| [{ 'header': [1, 2, 3, false] }], | |
| ['bold', 'italic', 'underline', 'strike'], | |
| [{ 'color': [] }, { 'background': [] }], | |
| [{ 'list': 'ordered'}, { 'list': 'bullet' }], | |
| ['link', 'image', 'video'], | |
| ['clean'] | |
| ] | |
| }, | |
| placeholder: 'Write your note here...' | |
| }); | |
| // Sample data for demonstration | |
| let notes = [ | |
| { | |
| id: '1', | |
| title: 'Introduction to Machine Learning', | |
| content: '<p>Machine learning is a subset of artificial intelligence that focuses on building systems that can learn from data.</p><p>Key concepts include supervised learning, unsupervised learning, and reinforcement learning.</p>', | |
| tags: ['AI', 'Machine Learning', 'Data Science'], | |
| starred: true, | |
| createdAt: new Date('2023-05-15'), | |
| updatedAt: new Date('2023-05-16'), | |
| connections: ['2', '3'] | |
| }, | |
| { | |
| id: '2', | |
| title: 'Neural Networks Fundamentals', | |
| content: '<p>Neural networks are computing systems inspired by biological neural networks.</p><p>They consist of layers of interconnected nodes (neurons) that process information.</p>', | |
| tags: ['AI', 'Neural Networks', 'Deep Learning'], | |
| starred: false, | |
| createdAt: new Date('2023-06-02'), | |
| updatedAt: new Date('2023-06-05'), | |
| connections: ['1', '3', '4'] | |
| }, | |
| { | |
| id: '3', | |
| title: 'Supervised Learning Techniques', | |
| content: '<p>Supervised learning involves training a model on labeled data.</p><p>Common algorithms include linear regression, logistic regression, and support vector machines.</p>', | |
| tags: ['Machine Learning', 'Supervised Learning', 'Algorithms'], | |
| starred: false, | |
| createdAt: new Date('2023-06-10'), | |
| updatedAt: new Date('2023-06-12'), | |
| connections: ['1', '2'] | |
| }, | |
| { | |
| id: '4', | |
| title: 'Deep Learning Architectures', | |
| content: '<p>Deep learning uses neural networks with multiple hidden layers.</p><p>Popular architectures include CNNs for image processing and RNNs for sequence data.</p>', | |
| tags: ['Deep Learning', 'Neural Networks', 'AI'], | |
| starred: true, | |
| createdAt: new Date('2023-07-01'), | |
| updatedAt: new Date('2023-07-05'), | |
| connections: ['2', '5'] | |
| }, | |
| { | |
| id: '5', | |
| title: 'Natural Language Processing Basics', | |
| content: '<p>NLP enables computers to understand, interpret, and generate human language.</p><p>Key tasks include sentiment analysis, machine translation, and text summarization.</p>', | |
| tags: ['NLP', 'AI', 'Text Processing'], | |
| starred: false, | |
| createdAt: new Date('2023-07-15'), | |
| updatedAt: new Date('2023-07-18'), | |
| connections: ['4'] | |
| } | |
| ]; | |
| let currentNoteId = null; | |
| let allTags = []; | |
| // Initialize Cytoscape for visualization | |
| let cy = cytoscape({ | |
| container: document.getElementById('cy'), | |
| elements: [], | |
| style: [ | |
| { | |
| selector: 'node', | |
| style: { | |
| 'label': 'data(label)', | |
| 'text-wrap': 'wrap', | |
| 'text-max-width': '100px', | |
| 'text-valign': 'center', | |
| 'text-halign': 'center', | |
| 'background-color': '#3b82f6', | |
| 'color': '#ffffff', | |
| 'width': '60', | |
| 'height': '60', | |
| 'font-size': '10' | |
| } | |
| }, | |
| { | |
| selector: 'node[type="current"]', | |
| style: { | |
| 'background-color': '#ef4444', | |
| 'width': '80', | |
| 'height': '80', | |
| 'font-size': '12' | |
| } | |
| }, | |
| { | |
| selector: 'node[type="connected"]', | |
| style: { | |
| 'background-color': '#10b981' | |
| } | |
| }, | |
| { | |
| selector: 'node[type="other"]', | |
| style: { | |
| 'background-color': '#9ca3af' | |
| } | |
| }, | |
| { | |
| selector: 'edge', | |
| style: { | |
| 'width': 2, | |
| 'line-color': '#9ca3af', | |
| 'curve-style': 'bezier', | |
| 'target-arrow-shape': 'triangle', | |
| 'target-arrow-color': '#9ca3af' | |
| } | |
| }, | |
| { | |
| selector: 'edge[type="strong"]', | |
| style: { | |
| 'line-color': '#3b82f6', | |
| 'width': 3, | |
| 'target-arrow-color': '#3b82f6' | |
| } | |
| } | |
| ], | |
| layout: { | |
| name: 'cose-bilkent', | |
| animate: true, | |
| randomize: true, | |
| nodeRepulsion: 4500, | |
| idealEdgeLength: 100, | |
| nodeDimensionsIncludeLabels: true | |
| } | |
| }); | |
| // Extract all tags from notes | |
| function extractTags() { | |
| allTags = []; | |
| notes.forEach(note => { | |
| note.tags.forEach(tag => { | |
| if (!allTags.includes(tag)) { | |
| allTags.push(tag); | |
| } | |
| }); | |
| }); | |
| renderTagCloud(); | |
| } | |
| // Render tag cloud | |
| function renderTagCloud() { | |
| const tagCloud = document.getElementById('tag-cloud'); | |
| tagCloud.innerHTML = ''; | |
| allTags.forEach(tag => { | |
| const tagElement = document.createElement('div'); | |
| tagElement.className = 'tag-chip px-2 py-1 bg-gray-100 text-gray-800 text-xs rounded-full cursor-pointer'; | |
| tagElement.textContent = tag; | |
| tagElement.addEventListener('click', () => filterByTag(tag)); | |
| tagCloud.appendChild(tagElement); | |
| }); | |
| } | |
| // Filter notes by tag | |
| function filterByTag(tag) { | |
| const filteredNotes = notes.filter(note => note.tags.includes(tag)); | |
| renderNotesList(filteredNotes); | |
| // Highlight the selected tag | |
| document.querySelectorAll('.tag-chip').forEach(chip => { | |
| if (chip.textContent === tag) { | |
| chip.classList.add('bg-blue-100', 'text-blue-800'); | |
| } else { | |
| chip.classList.remove('bg-blue-100', 'text-blue-800'); | |
| } | |
| }); | |
| } | |
| // Render notes list | |
| function renderNotesList(notesToRender = notes) { | |
| const notesList = document.getElementById('notes-list'); | |
| notesList.innerHTML = ''; | |
| // Sort by updated date (newest first) | |
| const sortedNotes = [...notesToRender].sort((a, b) => b.updatedAt - a.updatedAt); | |
| sortedNotes.forEach(note => { | |
| const noteElement = document.createElement('div'); | |
| noteElement.className = `note-card p-3 bg-white border border-gray-200 rounded-lg cursor-pointer transition-all ${currentNoteId === note.id ? 'border-blue-500 border-2' : ''}`; | |
| noteElement.innerHTML = ` | |
| <div class="flex justify-between items-start"> | |
| <h3 class="font-medium text-gray-800 truncate">${note.title}</h3> | |
| <div class="flex items-center"> | |
| ${note.starred ? '<i class="fas fa-star text-yellow-500 ml-2"></i>' : '<i class="far fa-star text-gray-300 ml-2"></i>'} | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-500 mt-1 line-clamp-2">${note.content.replace(/<[^>]*>/g, '').substring(0, 100)}</p> | |
| <div class="flex flex-wrap gap-1 mt-2"> | |
| ${note.tags.map(tag => `<span class="px-1.5 py-0.5 bg-gray-100 text-gray-600 text-xs rounded">${tag}</span>`).join('')} | |
| </div> | |
| <div class="flex justify-between items-center mt-2"> | |
| <span class="text-xs text-gray-400">${formatDate(note.updatedAt)}</span> | |
| <span class="text-xs text-blue-500">${note.connections.length} connections</span> | |
| </div> | |
| `; | |
| noteElement.addEventListener('click', () => loadNote(note.id)); | |
| notesList.appendChild(noteElement); | |
| }); | |
| } | |
| // Format date | |
| function formatDate(date) { | |
| const options = { year: 'numeric', month: 'short', day: 'numeric' }; | |
| return date.toLocaleDateString(undefined, options); | |
| } | |
| // Load a note into the editor | |
| function loadNote(noteId) { | |
| const note = notes.find(n => n.id === noteId); | |
| if (!note) return; | |
| currentNoteId = noteId; | |
| document.getElementById('note-title').value = note.title; | |
| quill.root.innerHTML = note.content; | |
| // Update tags input | |
| const tagsInput = document.getElementById('tags-input'); | |
| tagsInput.innerHTML = ''; | |
| note.tags.forEach(tag => { | |
| const tagElement = document.createElement('div'); | |
| tagElement.className = 'tag-chip px-2 py-1 bg-gray-100 text-gray-800 text-xs rounded-full flex items-center'; | |
| tagElement.innerHTML = ` | |
| ${tag} | |
| <button class="ml-1 text-gray-500 hover:text-gray-700" data-tag="${tag}"> | |
| <i class="fas fa-times text-xs"></i> | |
| </button> | |
| `; | |
| tagsInput.insertBefore(tagElement, document.getElementById('new-tag-input')); | |
| // Add event listener to remove tag | |
| tagElement.querySelector('button').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| removeTagFromNote(noteId, tag); | |
| }); | |
| }); | |
| // Update starred status | |
| const starButton = document.getElementById('star-note'); | |
| if (note.starred) { | |
| starButton.innerHTML = '<i class="fas fa-star text-yellow-500"></i>'; | |
| } else { | |
| starButton.innerHTML = '<i class="far fa-star"></i>'; | |
| } | |
| // Update connections list | |
| updateConnectionsList(noteId); | |
| // Re-render notes list to highlight current note | |
| renderNotesList(); | |
| } | |
| // Update connections list for a note | |
| function updateConnectionsList(noteId) { | |
| const note = notes.find(n => n.id === noteId); | |
| if (!note) return; | |
| const connectionsList = document.getElementById('connections-list'); | |
| connectionsList.innerHTML = ''; | |
| note.connections.forEach(connId => { | |
| const connectedNote = notes.find(n => n.id === connId); | |
| if (connectedNote) { | |
| const connElement = document.createElement('div'); | |
| connElement.className = 'px-3 py-1 bg-blue-50 text-blue-700 text-sm rounded-full flex items-center cursor-pointer'; | |
| connElement.innerHTML = ` | |
| ${connectedNote.title} | |
| <button class="ml-1 text-blue-400 hover:text-blue-700" data-connection="${connId}"> | |
| <i class="fas fa-times text-xs"></i> | |
| </button> | |
| `; | |
| connectionsList.appendChild(connElement); | |
| // Add event listener to remove connection | |
| connElement.querySelector('button').addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| removeConnection(noteId, connId); | |
| }); | |
| // Add event listener to navigate to connected note | |
| connElement.addEventListener('click', () => { | |
| loadNote(connId); | |
| }); | |
| } | |
| }); | |
| // Add "Add Connection" button if there are possible connections | |
| const possibleConnections = notes.filter(n => n.id !== noteId && !note.connections.includes(n.id)); | |
| if (possibleConnections.length > 0) { | |
| const addConnElement = document.createElement('div'); | |
| addConnElement.className = 'px-3 py-1 bg-gray-100 text-gray-700 text-sm rounded-full flex items-center cursor-pointer'; | |
| addConnElement.innerHTML = ` | |
| <i class="fas fa-plus mr-1"></i> Add Connection | |
| `; | |
| connectionsList.appendChild(addConnElement); | |
| addConnElement.addEventListener('click', () => { | |
| showConnectionModal(noteId); | |
| }); | |
| } | |
| } | |
| // Show modal to add connections | |
| function showConnectionModal(noteId) { | |
| // In a real app, this would be a proper modal | |
| // For this demo, we'll just connect to a random note | |
| const note = notes.find(n => n.id === noteId); | |
| if (!note) return; | |
| const possibleConnections = notes.filter(n => n.id !== noteId && !note.connections.includes(n.id)); | |
| if (possibleConnections.length > 0) { | |
| const randomConn = possibleConnections[Math.floor(Math.random() * possibleConnections.length)]; | |
| addConnection(noteId, randomConn.id); | |
| alert(`Connected to: ${randomConn.title}`); | |
| } | |
| } | |
| // Add a connection between notes | |
| function addConnection(noteId1, noteId2) { | |
| const note1 = notes.find(n => n.id === noteId1); | |
| const note2 = notes.find(n => n.id === noteId2); | |
| if (note1 && note2) { | |
| if (!note1.connections.includes(noteId2)) { | |
| note1.connections.push(noteId2); | |
| } | |
| if (!note2.connections.includes(noteId1)) { | |
| note2.connections.push(noteId1); | |
| } | |
| if (currentNoteId === noteId1) { | |
| updateConnectionsList(noteId1); | |
| } | |
| renderNotesList(); | |
| } | |
| } | |
| // Remove a connection between notes | |
| function removeConnection(noteId1, noteId2) { | |
| const note1 = notes.find(n => n.id === noteId1); | |
| const note2 = notes.find(n => n.id === noteId2); | |
| if (note1 && note2) { | |
| note1.connections = note1.connections.filter(id => id !== noteId2); | |
| note2.connections = note2.connections.filter(id => id !== noteId1); | |
| if (currentNoteId === noteId1) { | |
| updateConnectionsList(noteId1); | |
| } | |
| renderNotesList(); | |
| } | |
| } | |
| // Remove tag from note | |
| function removeTagFromNote(noteId, tag) { | |
| const note = notes.find(n => n.id === noteId); | |
| if (note) { | |
| note.tags = note.tags.filter(t => t !== tag); | |
| extractTags(); | |
| if (currentNoteId === noteId) { | |
| loadNote(noteId); | |
| } | |
| } | |
| } | |
| // Create a new note | |
| function createNewNote() { | |
| const newNote = { | |
| id: Date.now().toString(), | |
| title: 'Untitled Note', | |
| content: '', | |
| tags: [], | |
| starred: false, | |
| createdAt: new Date(), | |
| updatedAt: new Date(), | |
| connections: [] | |
| }; | |
| notes.unshift(newNote); | |
| extractTags(); | |
| renderNotesList(); | |
| loadNote(newNote.id); | |
| // Focus on title input | |
| document.getElementById('note-title').focus(); | |
| } | |
| // Save current note | |
| function saveCurrentNote() { | |
| if (!currentNoteId) return; | |
| const note = notes.find(n => n.id === currentNoteId); | |
| if (note) { | |
| note.title = document.getElementById('note-title').value; | |
| note.content = quill.root.innerHTML; | |
| note.updatedAt = new Date(); | |
| // Get tags from tags input | |
| const tagElements = document.querySelectorAll('#tags-input .tag-chip'); | |
| note.tags = Array.from(tagElements).map(el => el.textContent.trim()); | |
| renderNotesList(); | |
| } | |
| } | |
| // Delete current note | |
| function deleteCurrentNote() { | |
| if (!currentNoteId) return; | |
| if (confirm('Are you sure you want to delete this note?')) { | |
| // Remove this note from other notes' connections | |
| notes.forEach(note => { | |
| note.connections = note.connections.filter(connId => connId !== currentNoteId); | |
| }); | |
| // Remove the note itself | |
| notes = notes.filter(note => note.id !== currentNoteId); | |
| currentNoteId = null; | |
| extractTags(); | |
| renderNotesList(); | |
| // Clear editor | |
| document.getElementById('note-title').value = ''; | |
| quill.root.innerHTML = ''; | |
| document.getElementById('tags-input').innerHTML = ''; | |
| document.getElementById('connections-list').innerHTML = ''; | |
| document.getElementById('star-note').innerHTML = '<i class="far fa-star"></i>'; | |
| } | |
| } | |
| // Toggle star status of current note | |
| function toggleStarNote() { | |
| if (!currentNoteId) return; | |
| const note = notes.find(n => n.id === currentNoteId); | |
| if (note) { | |
| note.starred = !note.starred; | |
| const starButton = document.getElementById('star-note'); | |
| if (note.starred) { | |
| starButton.innerHTML = '<i class="fas fa-star text-yellow-500"></i>'; | |
| } else { | |
| starButton.innerHTML = '<i class="far fa-star"></i>'; | |
| } | |
| renderNotesList(); | |
| } | |
| } | |
| // Add tag to current note | |
| function addTagToCurrentNote(tag) { | |
| if (!currentNoteId || !tag) return; | |
| const note = notes.find(n => n.id === currentNoteId); | |
| if (note && !note.tags.includes(tag)) { | |
| note.tags.push(tag); | |
| extractTags(); | |
| loadNote(currentNoteId); | |
| } | |
| } | |
| // Visualize knowledge graph | |
| function visualizeKnowledgeGraph() { | |
| if (!currentNoteId) { | |
| visualizeFullGraph(); | |
| return; | |
| } | |
| // Show visualization view | |
| document.getElementById('editor-view').classList.add('hidden'); | |
| document.getElementById('visualization-view').classList.remove('hidden'); | |
| // Clear existing elements | |
| cy.elements().remove(); | |
| // Add current note as center node | |
| const currentNote = notes.find(n => n.id === currentNoteId); | |
| if (!currentNote) return; | |
| cy.add({ | |
| group: 'nodes', | |
| data: { | |
| id: currentNote.id, | |
| label: currentNote.title, | |
| type: 'current' | |
| } | |
| }); | |
| // Add connected notes | |
| currentNote.connections.forEach(connId => { | |
| const connectedNote = notes.find(n => n.id === connId); | |
| if (connectedNote) { | |
| cy.add({ | |
| group: 'nodes', | |
| data: { | |
| id: connectedNote.id, | |
| label: connectedNote.title, | |
| type: 'connected' | |
| } | |
| }); | |
| cy.add({ | |
| group: 'edges', | |
| data: { | |
| id: `${currentNote.id}-${connectedNote.id}`, | |
| source: currentNote.id, | |
| target: connectedNote.id, | |
| type: 'strong' | |
| } | |
| }); | |
| // Add second-level connections | |
| connectedNote.connections.forEach(secondConnId => { | |
| if (secondConnId !== currentNote.id && !currentNote.connections.includes(secondConnId)) { | |
| const secondConnectedNote = notes.find(n => n.id === secondConnId); | |
| if (secondConnectedNote) { | |
| // Check if node already exists | |
| if (!cy.$id(secondConnectedNote.id).length) { | |
| cy.add({ | |
| group: 'nodes', | |
| data: { | |
| id: secondConnectedNote.id, | |
| label: secondConnectedNote.title, | |
| type: 'other' | |
| } | |
| }); | |
| } | |
| // Check if edge already exists | |
| if (!cy.$id(`${connectedNote.id}-${secondConnectedNote.id}`).length) { | |
| cy.add({ | |
| group: 'edges', | |
| data: { | |
| id: `${connectedNote.id}-${secondConnectedNote.id}`, | |
| source: connectedNote.id, | |
| target: secondConnectedNote.id | |
| } | |
| }); | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| }); | |
| // Apply layout | |
| cy.layout({ name: 'cose-bilkent' }).run(); | |
| } | |
| // Visualize full knowledge graph | |
| function visualizeFullGraph() { | |
| // Show visualization view | |
| document.getElementById('editor-view').classList.add('hidden'); | |
| document.getElementById('visualization-view').classList.remove('hidden'); | |
| // Clear existing elements | |
| cy.elements().remove(); | |
| // Add all notes as nodes | |
| notes.forEach(note => { | |
| cy.add({ | |
| group: 'nodes', | |
| data: { | |
| id: note.id, | |
| label: note.title, | |
| type: note.id === currentNoteId ? 'current' : 'other' | |
| } | |
| }); | |
| }); | |
| // Add all connections as edges | |
| notes.forEach(note => { | |
| note.connections.forEach(connId => { | |
| // Only add edge once (avoid duplicates) | |
| if (note.id < connId) { | |
| cy.add({ | |
| group: 'edges', | |
| data: { | |
| id: `${note.id}-${connId}`, | |
| source: note.id, | |
| target: connId, | |
| type: note.id === currentNoteId || connId === currentNoteId ? 'strong' : 'normal' | |
| } | |
| }); | |
| } | |
| }); | |
| }); | |
| // Apply layout | |
| cy.layout({ name: 'cose-bilkent' }).run(); | |
| } | |
| // Close visualization view | |
| function closeVisualization() { | |
| document.getElementById('editor-view').classList.remove('hidden'); | |
| document.getElementById('visualization-view').classList.add('hidden'); | |
| } | |
| // Show AI suggestions | |
| function showAISuggestions() { | |
| document.getElementById('ai-panel').classList.remove('hidden'); | |
| } | |
| // Close AI suggestions | |
| function closeAISuggestions() { | |
| document.getElementById('ai-panel').classList.add('hidden'); | |
| } | |
| // Search notes | |
| function searchNotes(query) { | |
| if (!query) { | |
| renderNotesList(); | |
| return; | |
| } | |
| const lowerQuery = query.toLowerCase(); | |
| const filteredNotes = notes.filter(note => { | |
| return note.title.toLowerCase().includes(lowerQuery) || | |
| note.content.toLowerCase().includes(lowerQuery) || | |
| note.tags.some(tag => tag.toLowerCase().includes(lowerQuery)); | |
| }); | |
| renderNotesList(filteredNotes); | |
| // Highlight search terms in note cards | |
| document.querySelectorAll('.note-card').forEach(card => { | |
| const title = card.querySelector('h3'); | |
| const content = card.querySelector('p'); | |
| if (title && content) { | |
| const originalTitle = title.textContent; | |
| const originalContent = content.textContent; | |
| // Remove previous highlights | |
| title.innerHTML = originalTitle; | |
| content.innerHTML = originalContent; | |
| // Add new highlights | |
| if (lowerQuery) { | |
| const regex = new RegExp(lowerQuery, 'gi'); | |
| title.innerHTML = originalTitle.replace(regex, match => | |
| `<span class="search-highlight">${match}</span>` | |
| ); | |
| content.innerHTML = originalContent.replace(regex, match => | |
| `<span class="search-highlight">${match}</span>` | |
| ); | |
| } | |
| } | |
| }); | |
| } | |
| // Initialize text embedding (simulated for this demo) | |
| async function analyzeTextEmbedding() { | |
| if (!currentNoteId) return; | |
| const note = notes.find(n => n.id === currentNoteId); | |
| if (!note) return; | |
| // In a real app, this would use Universal Sentence Encoder or similar | |
| alert(`Analyzing text embeddings for: ${note.title}\n\nThis would generate vector representations of the text for semantic search and connection suggestions.`); | |
| // Simulate finding similar notes based on content | |
| const similarNotes = findSimilarNotes(note); | |
| if (similarNotes.length > 0) { | |
| const similarList = similarNotes.map(n => `- ${n.title}`).join('\n'); | |
| alert(`Potential similar notes based on content:\n${similarList}`); | |
| } | |
| } | |
| // Find similar notes (simulated semantic similarity) | |
| function findSimilarNotes(note) { | |
| // This is a simplified simulation - real app would use vector similarity | |
| const keywords = extractKeywords(note.content); | |
| if (keywords.length === 0) return []; | |
| return notes.filter(n => { | |
| if (n.id === note.id) return false; | |
| const otherKeywords = extractKeywords(n.content); | |
| return keywords.some(kw => otherKeywords.includes(kw)); | |
| }).slice(0, 3); // Return top 3 | |
| } | |
| // Extract keywords from content (simplified) | |
| function extractKeywords(content) { | |
| // Remove HTML tags | |
| const text = content.replace(/<[^>]*>/g, ''); | |
| // Simple keyword extraction - real app would use NLP techniques | |
| const commonWords = ['the', 'and', 'that', 'have', 'for', 'not', 'with', 'you', 'this', 'but']; | |
| const words = text.toLowerCase().split(/\s+/); | |
| return [...new Set(words.filter(word => | |
| word.length > 3 && | |
| !commonWords.includes(word) && | |
| /^[a-z]+$/.test(word) | |
| ))].slice(0, 5); // Return top 5 unique keywords | |
| } | |
| // Event listeners | |
| document.getElementById('new-note-btn').addEventListener('click', createNewNote); | |
| document.getElementById('star-note').addEventListener('click', toggleStarNote); | |
| document.getElementById('delete-note').addEventListener('click', deleteCurrentNote); | |
| document.getElementById('visualize-btn').addEventListener('click', visualizeKnowledgeGraph); | |
| document.getElementById('ai-suggest-btn').addEventListener('click', showAISuggestions); | |
| document.getElementById('embedding-btn').addEventListener('click', analyzeTextEmbedding); | |
| document.getElementById('close-visualization').addEventListener('click', closeVisualization); | |
| document.getElementById('close-ai').addEventListener('click', closeAISuggestions); | |
| document.getElementById('search-input').addEventListener('input', (e) => searchNotes(e.target.value)); | |
| // Layout buttons | |
| document.getElementById('layout-force').addEventListener('click', () => { | |
| cy.layout({ name: 'cose-bilkent' }).run(); | |
| }); | |
| document.getElementById('layout-hierarchical').addEventListener('click', () => { | |
| cy.layout({ name: 'dagre' }).run(); | |
| }); | |
| document.getElementById('layout-concentric').addEventListener('click', () => { | |
| cy.layout({ name: 'concentric' }).run(); | |
| }); | |
| // Map view buttons | |
| document.querySelectorAll('.map-view-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const view = btn.dataset.view; | |
| if (view === 'full') { | |
| visualizeFullGraph(); | |
| } else { | |
| visualizeKnowledgeGraph(); | |
| } | |
| }); | |
| }); | |
| // Filter buttons | |
| document.querySelectorAll('.filter-btn').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| const filter = btn.dataset.filter; | |
| let filteredNotes = []; | |
| if (filter === 'recent') { | |
| filteredNotes = [...notes].sort((a, b) => b.updatedAt - a.updatedAt); | |
| } else if (filter === 'starred') { | |
| filteredNotes = notes.filter(note => note.starred); | |
| } else if (filter === 'connected') { | |
| filteredNotes = [...notes].sort((a, b) => b.connections.length - a.connections.length); | |
| } | |
| renderNotesList(filteredNotes); | |
| }); | |
| }); | |
| // Tag input | |
| document.getElementById('new-tag-input').addEventListener('keydown', (e) => { | |
| if (e.key === 'Enter' || e.key === ',') { | |
| e.preventDefault(); | |
| const tag = e.target.value.trim(); | |
| if (tag) { | |
| addTagToCurrentNote(tag); | |
| e.target.value = ''; | |
| } | |
| } | |
| }); | |
| // Auto-save when leaving editor | |
| quill.on('text-change', () => { | |
| if (currentNoteId) { | |
| saveCurrentNote(); | |
| } | |
| }); | |
| document.getElementById('note-title').addEventListener('blur', saveCurrentNote); | |
| // Initialize the app | |
| extractTags(); | |
| renderNotesList(); | |
| // Load first note by default for demo | |
| if (notes.length > 0) { | |
| loadNote(notes[0].id); | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=shri210620/ai-notes" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |