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> |