brainy/examples/demo.html
David Snelling cfe1fd9e22 **feat: refine README and enhance embedding workflows**
### Changes:
- Updated README.md:
  - Refined the project description to highlight its lightweight and powerful nature.
  - Enhanced feature list, replacing outdated entries with modern updates (e.g., TensorFlow integration replacing LLM creation).
  - Included detailed installation and usage instructions for TensorFlow.js support and embedding workflows.
  - Added "Usage Options" section to define different import methods and supported environments.
  - Expanded "Backup and Restore" details, showcasing CLI and scripting examples.
  - Introduced optimized HNSW indexing for large datasets.
  - Replaced examples with a unified and simplified demonstration approach.
  - Embedded advanced build and environment-specific configurations for better clarity.
- Removed outdated content related to language model generation, including LLM-related commands and examples.
- Added README.demo.md with full instructions for running the interactive demo.

### Purpose:
This update modernizes the documentation and focuses on Brainy’s current capabilities, simplifying onboarding for new users while removing deprecated features. It also streamlines project understanding, enabling developers to better utilize its features in diverse environments.
2025-06-19 15:00:08 -07:00

3420 lines
107 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brainy Interactive Demo - A Lightweight Graph & Vector Data Platform</title>
<link rel="icon" href="../brainy.png" type="image/png">
<!--
IMPORTANT: This demo must be served over HTTP, not opened directly as a file.
Run the following command from the project root directory:
npx http-server
Then navigate to http://localhost:8080/examples/demo.html in your browser.
-->
<!-- D3.js for visualizations -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Material Icons font -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<style>
:root {
--primary-color: #193c41;
--secondary-color: #3f6e65;
--accent-color: #c95e2f;
--danger-color: #b84c26;
--text-color: #333;
--light-bg: #ececec;
--dark-bg: #333;
--card-bg: #fff;
--border-radius: 8px;
--box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
/* Cartographer colors */
--md3-primary: #6750A4;
--md3-on-primary: #FFFFFF;
--md3-secondary-container: #E8DEF8;
--md3-on-secondary-container: #1D192B;
--md3-outline: #79747E;
--md3-surface: #FFFBFE;
--md3-shadow: rgba(0, 0, 0, 0.3);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-color);
margin: 0;
padding: 0;
background-color: var(--light-bg);
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
padding: 40px 0;
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
margin-bottom: 30px;
}
header img {
width: 150px;
margin-bottom: 20px;
}
h1, h2, h3 {
color: var(--primary-color);
}
.demo-section {
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 30px;
margin-bottom: 30px;
}
.demo-controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 20px 0;
}
button {
background-color: var(--primary-color);
color: white;
border: none;
padding: 10px 20px;
border-radius: var(--border-radius);
cursor: pointer;
transition: var(--transition);
}
button:hover {
background-color: #388E3C;
transform: translateY(-2px);
}
button.secondary {
background-color: var(--secondary-color);
}
button.secondary:hover {
background-color: #1976D2;
}
button.accent {
background-color: var(--accent-color);
}
button.accent:hover {
background-color: #F57C00;
}
button.danger {
background-color: var(--danger-color);
}
button.danger:hover {
background-color: #D32F2F;
}
input, select, textarea {
padding: 10px;
border: 1px solid #ddd;
border-radius: var(--border-radius);
flex-grow: 1;
}
textarea {
min-height: 100px;
font-family: monospace;
}
.visualization {
height: 500px;
background-color: var(--light-bg);
border-radius: var(--border-radius);
margin: 20px 0;
position: relative;
overflow: hidden;
}
/* Cartographer styles */
.zoom-controls {
position: absolute;
bottom: 1.5rem;
right: 1rem;
z-index: 10;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.zoom-fit-button {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: var(--md3-primary);
color: var(--md3-on-primary);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0.125rem 0.25rem var(--md3-shadow);
transition: background-color 0.2s, transform 0.2s;
}
.zoom-fit-button:hover {
background-color: #7B68B5;
transform: scale(1.05);
}
.zoom-fit-button:active {
background-color: #553B93;
transform: scale(0.95);
}
.graph-container {
width: 100%;
height: 100%;
background-color: #FDF8FF;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
overflow: hidden;
}
.graph-container svg {
display: block;
width: 100%;
height: 100%;
font-family: 'Roboto', 'Inter', sans-serif;
}
.graph-container .edges line {
transition: stroke-opacity 0.2s ease-in-out;
}
.graph-container .node-group {
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1), filter 0.3s ease;
transform-origin: center;
}
.graph-container .node-group:hover {
filter: drop-shadow(0 0.5rem 1rem rgba(0, 0, 0, 0.25));
animation: jiggle 0.5s ease;
}
.graph-container .node-group path {
transition: fill-opacity 0.15s ease-in-out;
}
.graph-container .overlay-container {
pointer-events: none;
}
.graph-container .overlay-container .node-info-popup {
pointer-events: auto;
}
@keyframes jiggle {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(0.5deg) scale(1.01);
}
50% {
transform: rotate(-0.5deg) scale(1.01);
}
75% {
transform: rotate(0.5deg) scale(1.01);
}
100% {
transform: rotate(0deg);
}
}
.results {
background-color: var(--light-bg);
border-radius: var(--border-radius);
padding: 15px;
max-height: 300px;
overflow-y: auto;
font-family: monospace;
white-space: pre-wrap;
}
.tabs {
display: flex;
margin-bottom: 20px;
flex-wrap: wrap;
}
.tab {
padding: 10px 20px;
background-color: var(--light-bg);
cursor: pointer;
border-radius: var(--border-radius) var(--border-radius) 0 0;
margin-right: 5px;
margin-bottom: 5px;
}
.tab.active {
background-color: var(--primary-color);
color: white;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.section-content, .subsection-content {
display: block;
margin-bottom: 30px;
}
.quickstart-section {
background: linear-gradient(135deg, #193c41, #3f6e65);
color: white;
padding: 30px;
border-radius: var(--border-radius);
margin-bottom: 30px;
box-shadow: var(--box-shadow);
}
.quickstart-section h2 {
color: white;
margin-top: 0;
}
.code-tabs {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-top: 20px;
}
.code-tab {
background-color: rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
padding: 20px;
}
.code-tab h3 {
margin-top: 0;
color: white;
}
.code-tab p {
color: rgba(255, 255, 255, 0.9);
}
.section-divider {
height: 1px;
background-color: var(--light-bg);
margin: 20px 0;
}
.code-block {
background-color: var(--dark-bg);
color: white;
padding: 15px;
border-radius: var(--border-radius);
overflow-x: auto;
margin: 20px 0;
font-family: monospace;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin: 30px 0;
}
.feature-card {
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
padding: 20px;
transition: var(--transition);
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.feature-card h3 {
margin-top: 0;
color: var(--primary-color);
}
.tooltip {
position: absolute;
padding: 8px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 4px;
pointer-events: none;
z-index: 10;
font-size: 12px;
}
.cli-terminal {
background-color: var(--dark-bg);
color: #fff;
padding: 15px;
border-radius: var(--border-radius);
font-family: monospace;
margin: 20px 0;
position: relative;
}
.cli-prompt {
color: var(--accent-color);
}
.cli-input {
background: transparent;
border: none;
color: #fff;
font-family: monospace;
width: 80%;
outline: none;
}
.cli-output {
margin-top: 10px;
white-space: pre-wrap;
max-height: 300px;
overflow-y: auto;
}
.cli-history {
margin-bottom: 10px;
}
.cli-command {
margin-bottom: 5px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.features-overview {
background: linear-gradient(135deg, #193c41, #3f6e65);
color: white;
padding: 30px;
border-radius: var(--border-radius);
margin-bottom: 30px;
box-shadow: var(--box-shadow);
}
.features-overview h2 {
color: white;
margin-top: 0;
}
.features-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-top: 20px;
}
.feature-item {
background-color: var(--card-bg); /* Changed from transparent white to card background */
padding: 15px;
border-radius: var(--border-radius);
/* backdrop-filter: blur(10px); Removed as it's less relevant with solid background */
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); /* Added a light shadow to feature items */
}
.feature-item h4 {
margin: 0 0 10px 0;
color: var(--secondary-color); /* Changed from white to secondary color for better contrast */
}
.feature-item p {
margin: 0;
font-size: 0.9em;
opacity: 1; /* Changed from 0.9 to 1 for better readability */
color: var(--text-color); /* Ensure paragraph text is also dark */
}
footer {
text-align: center;
padding: 20px;
margin-top: 40px;
background-color: var(--card-bg);
border-radius: var(--border-radius);
box-shadow: var(--box-shadow);
}
@media (max-width: 768px) {
.container {
padding: 10px;
}
.demo-controls {
flex-direction: column;
}
.feature-grid {
grid-template-columns: 1fr;
}
.features-list {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div id="http-warning"
style="background-color: #f44336; color: white; padding: 15px; margin-bottom: 20px; border-radius: 8px; display: none;">
<strong>Warning:</strong> This demo must be served over HTTP to work properly. If you're seeing this message or
the demo isn't working, please:
<ol style="margin-top: 10px; margin-bottom: 5px;">
<li>Open a terminal in the project root directory</li>
<li>Run: <code style="background-color: rgba(0,0,0,0.2); padding: 3px 5px; border-radius: 3px;">npx
http-server</code></li>
<li>Navigate to <a href="http://localhost:8080/examples/demo.html"
style="color: white; text-decoration: underline;">http://localhost:8080/examples/demo.html</a>
</li>
</ol>
<button onclick="this.parentElement.style.display='none'"
style="background-color: white; color: #f44336; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; float: right;">
Dismiss
</button>
</div>
<header>
<img src="../brainy.png" alt="Brainy Logo">
<h1>Brainy Interactive Demo</h1>
<p>A lightweight but powerful graph & vector data platform for AI applications across any environment</p>
</header>
<!-- Features Overview Section -->
<section class="features-overview">
<h2>🚀 Key Features</h2>
<p>Explore all of Brainy's powerful capabilities in this interactive demo:</p>
<div class="features-list">
<div class="feature-item">
<h4>🏃‍♂️ Run Everywhere</h4>
<p>Run Brainy in a browser, container, serverless cloud service or the terminal!</p>
</div>
<div class="feature-item">
<h4>🔍 Vector Search</h4>
<p>Find semantically similar content using embeddings (like having ESP for your data!)</p>
</div>
<div class="feature-item">
<h4>🌐 Graph Database</h4>
<p>Connect data with meaningful relationships (your data's social network)</p>
</div>
<div class="feature-item">
<h4>🌊 Streaming Pipeline</h4>
<p>Process data in real-time as it flows through the system (like a data waterslide!)</p>
</div>
<div class="feature-item">
<h4>🧩 Extensible Augmentations</h4>
<p>Customize and extend functionality with pluggable components (LEGO blocks for your data!)</p>
</div>
<div class="feature-item">
<h4>🔌 Built-in Conduits</h4>
<p>Sync and scale across instances with WebSocket and WebRTC (your data's teleportation system!)</p>
</div>
<div class="feature-item">
<h4>🤖 TensorFlow Integration</h4>
<p>Use TensorFlow.js for high-quality embeddings (included as a required dependency)</p>
</div>
<div class="feature-item">
<h4>🧠 Adaptive Intelligence</h4>
<p>Automatically optimizes for your environment and usage patterns</p>
</div>
<div class="feature-item">
<h4>🌍 Cross-Platform</h4>
<p>Works everywhere you do: browsers, Node.js, and server environments</p>
</div>
<div class="feature-item">
<h4>💾 Persistent Storage</h4>
<p>Data persists across sessions and scales to any size (no memory loss here!)</p>
</div>
<div class="feature-item">
<h4>🔧 TypeScript Support</h4>
<p>Fully typed API with generics (for those who like their code tidy)</p>
</div>
<div class="feature-item">
<h4>📱 CLI Tools</h4>
<p>Powerful command-line interface for data management (command line wizardry)</p>
</div>
<div class="feature-item">
<h4>📊 Data Visualization</h4>
<p>Interactive graph and vector space visualizations</p>
</div>
<div class="feature-item">
<h4>☁️ Cloud Ready</h4>
<p>Easy deployment to Google, AWS, Azure, and other cloud platforms</p>
</div>
</div>
</section>
<!-- Quick Start Section -->
<section class="quickstart-section">
<h2>🏁 Quick Start</h2>
<div class="code-tabs">
<div class="code-tab">
<h3>Node.js Environment</h3>
<pre class="code-block">
import {BrainyData, NounType, VerbType} from '@soulcraft/brainy'
// Create and initialize the database
const db = new BrainyData()
await db.init()
// Add data (automatically converted to vectors)
const catId = await db.add("Cats are independent pets", {
noun: NounType.Thing,
category: 'animal'
})
const dogId = await db.add("Dogs are loyal companions", {
noun: NounType.Thing,
category: 'animal'
})
// Search for similar items
const results = await db.searchText("feline pets", 2)
console.log(results)
// Returns items similar to "feline pets" with similarity scores
// Add a relationship between items
await db.addVerb(catId, dogId, {
verb: VerbType.RelatedTo,
description: 'Both are common household pets'
})
</pre>
</div>
<div class="code-tab">
<h3>Browser Environment</h3>
<p>You can import the browser-optimized version in two ways:</p>
<h4>Option 1: Using the browser-specific import</h4>
<pre class="code-block">
// Import the browser-optimized version
import {BrainyData, NounType, VerbType} from '@soulcraft/brainy/browser'
// Use the same API as in Node.js
const db = new BrainyData()
await db.init()
// ...
</pre>
<h4>Option 2: Using a script tag in HTML</h4>
<pre class="code-block">
&lt;script type="module"&gt;
// Import Brainy library directly as a module
import {BrainyData, NounType, VerbType} from '../node_modules/@soulcraft/brainy/dist/brainy.js'
// Use the same API as in Node.js
const db = new BrainyData()
await db.init()
// ...
&lt;/script&gt;
</pre>
<p>Modern bundlers like Webpack, Rollup, and Vite will automatically select the browser-optimized version when
targeting browser environments.</p>
</div>
</div>
</section>
<section class="demo-section">
<h2>Interactive Brainy Explorer</h2>
<p>This interactive demo allows you to experiment with all of Brainy's features. Initialize the database, add
data, create relationships, and perform searches - all with real-time visualizations.</p>
<!-- All sections are now visible without tabs -->
<!-- Database Section -->
<div class="section-content" id="database-content">
<h3>Database Operations</h3>
<p>Initialize the database, add sample data, or clear all data.</p>
<div class="demo-controls">
<button id="init-db-btn">Initialize Database</button>
<button id="add-sample-btn" class="secondary">Add Sample Data</button>
<button id="clear-db-btn" class="danger">Clear Database</button>
<button id="export-data-btn">Backup Data</button>
<button id="import-data-btn">Restore Data</button>
<button id="import-sparse-data-btn" class="secondary">Restore Sparse Data</button>
</div>
<div class="results" id="db-status">
Database not initialized. Click "Initialize Database" to start.
</div>
<div class="visualization" id="db-visualization">
<div class="zoom-controls">
<button class="zoom-fit-button" id="zoom-fit-btn" title="Zoom to fit all nodes">
<i class="material-icons">fit_screen</i>
</button>
</div>
<div class="graph-container">
<div style="text-align: center; padding-top: 200px; color: #666;">
Database visualization will appear here after initialization
</div>
</div>
</div>
</div>
<!-- Data Model Section -->
<div class="section-content" id="data-model-content">
<h3>Data Model Management</h3>
<p>Create entities (nouns), define relationships (verbs), and manage your data schema.</p>
<div class="section-divider"></div>
<!-- Nouns Section -->
<div class="subsection-content" id="nouns-content">
<div class="form-group">
<label for="noun-type">Noun Type:</label>
<select id="noun-type">
<option value="person">Person</option>
<option value="place">Place</option>
<option value="thing">Thing</option>
<option value="event">Event</option>
<option value="concept">Concept</option>
<option value="content">Content</option>
<option value="group">Group</option>
<option value="list">List</option>
<option value="category">Category</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group">
<label for="noun-id">ID (optional):</label>
<input type="text" id="noun-id" placeholder="Leave blank for auto-generated ID">
</div>
<div class="form-group">
<label for="noun-name">Name:</label>
<input type="text" id="noun-name" placeholder="Name of the entity">
</div>
<div class="form-group">
<label for="noun-content">Description/Content:</label>
<textarea id="noun-content" placeholder="Description or content of the entity"></textarea>
</div>
<div class="form-group">
<label for="noun-tags">Tags (comma-separated):</label>
<input type="text" id="noun-tags" placeholder="tag1, tag2, tag3">
</div>
<div class="demo-controls">
<button id="add-noun-btn">Add Noun</button>
<button id="query-nouns-btn" class="accent">Query Nouns</button>
</div>
</div>
<!-- Verbs Section -->
<div class="subsection-content" id="verbs-content">
<div class="form-group">
<label for="verb-type">Verb Type:</label>
<select id="verb-type">
<option value="attributedTo">Attributed To</option>
<option value="controls">Controls</option>
<option value="created">Created</option>
<option value="earned">Earned</option>
<option value="owns">Owns</option>
<option value="memberOf">Member Of</option>
<option value="relatedTo">Related To</option>
<option value="worksWith">Works With</option>
<option value="friendOf">Friend Of</option>
<option value="reportsTo">Reports To</option>
<option value="supervises">Supervises</option>
<option value="mentors">Mentors</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group">
<label for="source-id">Source Noun:</label>
<select id="source-id">
<option value="">-- Select Source Noun --</option>
</select>
<button id="refresh-nouns-btn" class="secondary" style="margin-top: 5px; padding: 5px 10px;">Refresh Noun
List
</button>
</div>
<div class="form-group">
<label for="target-id">Target Noun:</label>
<select id="target-id">
<option value="">-- Select Target Noun --</option>
</select>
</div>
<div class="form-group">
<label for="verb-strength">Relationship Strength (0.0 - 1.0):</label>
<input type="number" id="verb-strength" placeholder="0.8" min="0" max="1" step="0.1" value="0.8">
</div>
<div class="form-group">
<label for="verb-description">Relationship Description:</label>
<textarea id="verb-description"
placeholder="Describe the relationship between the source and target"></textarea>
</div>
<div class="demo-controls">
<button id="add-verb-btn">Add Verb</button>
<button id="query-verbs-btn" class="accent">Query Verbs</button>
</div>
</div>
<div class="results" id="entity-results">
Entity operations will appear here...
</div>
</div>
<!-- Vector Search Section -->
<div class="section-content" id="search-content">
<h3>Vector Search & Semantic Queries</h3>
<p>Perform semantic searches using vector embeddings and similarity matching.</p>
<div class="form-group">
<label for="search-query">Search Query:</label>
<input type="text" id="search-query" placeholder="Enter your search query...">
</div>
<div class="form-group">
<label for="search-type">Search Type:</label>
<select id="search-type">
<option value="semantic">Semantic Search</option>
<option value="keyword">Keyword Search</option>
<option value="hybrid">Hybrid Search</option>
</select>
</div>
<div class="demo-controls">
<button id="search-btn">Search</button>
<button id="similar-entities-btn" class="secondary">Find Similar</button>
<button id="cluster-analysis-btn" class="accent">Cluster Analysis</button>
</div>
<div class="results" id="search-results">
Search results will appear here...
</div>
<div class="visualization" id="search-visualization">
<div style="text-align: center; padding-top: 200px; color: #666;">
Search results visualization
</div>
</div>
</div>
<!-- Visualization Section -->
<div class="section-content" id="visualization-content">
<h3>Data Visualization</h3>
<p>Interactive visualizations of your graph data and vector spaces.</p>
<div class="demo-controls">
<button id="graph-viz-btn">Graph View</button>
<button id="vector-space-btn" class="secondary">Vector Space</button>
<button id="timeline-btn" class="accent">Timeline View</button>
<button id="heatmap-btn" class="accent">Similarity Heatmap</button>
</div>
<div class="visualization" id="main-visualization">
<div style="text-align: center; padding-top: 200px; color: #666;">
Select a visualization type above
</div>
</div>
<div class="demo-controls">
<label>
<input type="range" id="zoom-slider" min="0.1" max="3" step="0.1" value="1">
Zoom
</label>
<button id="reset-view-btn">Reset View</button>
<button id="export-viz-btn" class="secondary">Export Visualization</button>
</div>
</div>
<!-- CLI Commands Section -->
<div class="section-content" id="cli-content">
<h3>CLI Command Interface</h3>
<p>Simulate CLI commands and explore Brainy's command-line interface.</p>
<div class="cli-terminal">
<div class="cli-history" id="cli-history"></div>
<div class="cli-command">
<span class="cli-prompt">brainy$</span>
<input type="text" class="cli-input" id="cli-input" placeholder="Type a command...">
</div>
<div class="cli-output" id="cli-output"></div>
</div>
<div class="demo-controls">
<button id="help-cmd-btn">Help</button>
<button id="status-cmd-btn" class="secondary">Status</button>
<button id="init-cmd-btn" class="accent">Init</button>
<button id="query-cmd-btn" class="accent">Query</button>
<button id="clear-terminal-btn" class="danger">Clear</button>
</div>
<div class="code-block">
<strong>Available Commands:</strong><br>
brainy init - Initialize a new database<br>
brainy add &lt;data&gt; - Add data to the database<br>
brainy search &lt;query&gt; - Search for entities<br>
brainy relate &lt;from&gt; &lt;to&gt; - Create relationships<br>
brainy export - Export database<br>
brainy status - Show database status<br>
brainy help - Show all commands
</div>
</div>
<!-- Feature Demo Section -->
<div class="section-content" id="features-content">
<h3>Feature Demonstrations</h3>
<p>Explore specific features and capabilities of Brainy with guided demonstrations.</p>
<div class="feature-grid">
<div class="feature-card">
<h3>🔍 HNSW Vector Search</h3>
<p>Demonstrate fast approximate nearest neighbor search with hierarchical navigable small world
graphs.</p>
<button class="demo-feature-btn" data-feature="hnsw">Try HNSW Demo</button>
</div>
<div class="feature-card">
<h3>🌐 Graph Traversal</h3>
<p>Show graph traversal algorithms and relationship queries.</p>
<button class="demo-feature-btn" data-feature="graph-traversal">Try Graph Demo</button>
</div>
<div class="feature-card">
<h3>🧠 Embedding Pipeline</h3>
<p>Demonstrate automatic text-to-vector conversion and similarity computation.</p>
<button class="demo-feature-btn" data-feature="embedding">Try Embedding Demo</button>
</div>
<div class="feature-card">
<h3>📊 Real-time Updates</h3>
<p>Show live data synchronization and event streaming capabilities.</p>
<button class="demo-feature-btn" data-feature="realtime">Try Real-time Demo</button>
</div>
<div class="feature-card">
<h3>💾 Storage Backends</h3>
<p>Compare different storage options: memory, filesystem, and cloud.</p>
<button class="demo-feature-btn" data-feature="storage">Try Storage Demo</button>
</div>
<div class="feature-card">
<h3>🔧 TypeScript Integration</h3>
<p>Show type-safe operations and schema validation.</p>
<button class="demo-feature-btn" data-feature="typescript">Try TypeScript Demo</button>
</div>
</div>
<div class="results" id="feature-demo-results">
Select a feature above to see a detailed demonstration...
</div>
</div>
</section>
<footer>
<p>&copy; 2024 Brainy - A lightweight graph & vector data platform</p>
<p>
<a href="https://github.com/soulcraft/brainy" target="_blank">GitHub</a> |
<a href="../README.md" target="_blank">Documentation</a> |
<a href="https://npm.im/@soulcraft/brainy" target="_blank">NPM</a>
</p>
</footer>
</div>
<script type="module">
// Import Brainy library directly as a module
import { BrainyData } from '../dist/brainy.js'
// Make BrainyData available globally
window.BrainyData = BrainyData
// Initialize demo when DOM is loaded
document.addEventListener('DOMContentLoaded', async function() {
// Check if we're running over HTTP
if (window.location.protocol === 'file:') {
document.getElementById('http-warning').style.display = 'block'
}
try {
// Verify the imported module has the required exports
if (BrainyData) {
initializeDemo()
} else {
showError('Brainy library not found. Please build the project first by running: npm run build')
}
} catch (error) {
showError('Failed to initialize demo: ' + error.message)
}
})
// Tab switching functionality removed - all sections are now visible
// Initialize the demo
function initializeDemo() {
log('db-status', 'Brainy library loaded successfully!')
// Initialize event listeners
setupEventListeners()
// Initialize CLI
setupCLI()
// Initialize visualizations
setupVisualizations()
// Try to populate noun dropdowns with any existing data
setTimeout(() => {
try {
if (database) {
populateNounDropdowns()
}
} catch (error) {
console.log('No database initialized yet, noun dropdowns will be populated later')
}
}, 1000)
}
// Setup all event listeners
function setupEventListeners() {
// Database operations
document.getElementById('init-db-btn').addEventListener('click', initializeDatabase)
document.getElementById('add-sample-btn').addEventListener('click', addSampleData)
document.getElementById('clear-db-btn').addEventListener('click', clearDatabase)
document.getElementById('export-data-btn').addEventListener('click', backupData)
document.getElementById('import-data-btn').addEventListener('click', restoreData)
document.getElementById('import-sparse-data-btn').addEventListener('click', restoreSparseData)
// Data model operations - Legacy buttons
document.getElementById('add-entity-btn')?.addEventListener('click', addEntity)
document.getElementById('create-relationship-btn')?.addEventListener('click', createRelationship)
document.getElementById('query-entities-btn')?.addEventListener('click', queryEntities)
// Data model tabs code removed - all sections are now visible
// Always populate noun dropdowns for the verbs section
populateNounDropdowns()
// Noun operations
document.getElementById('add-noun-btn')?.addEventListener('click', addNoun)
document.getElementById('query-nouns-btn')?.addEventListener('click', queryNouns)
// Verb operations
document.getElementById('add-verb-btn')?.addEventListener('click', addVerb)
document.getElementById('query-verbs-btn')?.addEventListener('click', queryVerbs)
document.getElementById('refresh-nouns-btn')?.addEventListener('click', populateNounDropdowns)
// Search operations
document.getElementById('search-btn').addEventListener('click', performSearch)
document.getElementById('similar-entities-btn').addEventListener('click', findSimilarEntities)
document.getElementById('cluster-analysis-btn').addEventListener('click', performClusterAnalysis)
// Augmentation operations removed
// Visualization controls
document.getElementById('graph-viz-btn').addEventListener('click', () => showVisualization('graph'))
document.getElementById('vector-space-btn').addEventListener('click', () => showVisualization('vector'))
document.getElementById('timeline-btn').addEventListener('click', () => showVisualization('timeline'))
document.getElementById('heatmap-btn').addEventListener('click', () => showVisualization('heatmap'))
document.getElementById('reset-view-btn').addEventListener('click', resetVisualization)
document.getElementById('export-viz-btn').addEventListener('click', exportVisualization)
// CLI commands
document.getElementById('help-cmd-btn').addEventListener('click', () => executeCLICommand('help'))
document.getElementById('status-cmd-btn').addEventListener('click', () => executeCLICommand('status'))
document.getElementById('init-cmd-btn').addEventListener('click', () => executeCLICommand('init'))
document.getElementById('query-cmd-btn').addEventListener('click', () => executeCLICommand('query sample'))
document.getElementById('clear-terminal-btn').addEventListener('click', clearTerminal)
// Feature demonstrations
document.querySelectorAll('.demo-feature-btn').forEach(btn => {
btn.addEventListener('click', function() {
const feature = this.dataset.feature
demonstrateFeature(feature)
})
})
}
// Setup CLI functionality
function setupCLI() {
const cliInput = document.getElementById('cli-input')
cliInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const command = this.value.trim()
if (command) {
executeCLICommand(command)
this.value = ''
}
}
})
}
// Database operations
let database = null
async function initializeDatabase() {
try {
log('db-status', 'Initializing database...')
// Initialize Brainy database
database = new BrainyData({
storage: { type: 'opfs' },
augmentations: ['embedding', 'search']
})
await database.init()
log('db-status', 'Database initialized successfully!\nStorage: OPFS\nAugmentations: Embedding, Search')
updateDatabaseVisualization()
// Populate noun dropdowns after initialization
populateNounDropdowns()
} catch (error) {
log('db-status', 'Error initializing database: ' + error.message)
}
}
async function addSampleData() {
if (!database) {
log('db-status', 'Please initialize database first')
return
}
try {
log('db-status', 'Adding sample data...')
// Sample Nouns with various types from the schema
const sampleNouns = [
// Person nouns
{
id: 'person1',
type: 'person',
noun: 'person',
name: 'John Smith',
content: 'Software engineer with expertise in AI and machine learning.',
tags: ['engineer', 'developer']
},
{
id: 'person2',
type: 'person',
noun: 'person',
name: 'Emily Johnson',
content: 'Data scientist specializing in natural language processing.',
tags: ['scientist', 'researcher']
},
{
id: 'person3',
type: 'person',
noun: 'person',
name: 'Michael Chen',
content: 'Product manager for database technologies.',
tags: ['manager', 'product']
},
// Place nouns
{
id: 'place1',
type: 'place',
noun: 'place',
name: 'Silicon Valley',
content: 'Technology hub in Northern California.',
tags: ['tech', 'innovation']
},
{
id: 'place2',
type: 'place',
noun: 'place',
name: 'Cambridge',
content: 'University city with strong research focus.',
tags: ['education', 'research']
},
// Thing nouns
{
id: 'thing1',
type: 'thing',
noun: 'thing',
name: 'Neural Network',
content: 'Computational model inspired by the human brain.',
tags: ['AI', 'computing']
},
{
id: 'thing2',
type: 'thing',
noun: 'thing',
name: 'Graph Database',
content: 'Database that uses graph structures for semantic queries.',
tags: ['database', 'storage']
},
// Event nouns
{
id: 'event1',
type: 'event',
noun: 'event',
name: 'AI Conference 2023',
content: 'Annual conference on artificial intelligence advancements.',
tags: ['conference', 'AI']
},
{
id: 'event2',
type: 'event',
noun: 'event',
name: 'Database Summit',
content: 'Industry summit on database technologies and trends.',
tags: ['summit', 'database']
},
// Concept nouns
{
id: 'concept1',
type: 'concept',
noun: 'concept',
name: 'Machine Learning',
content: 'Field of study that gives computers the ability to learn without being explicitly programmed.',
tags: ['AI', 'algorithms']
},
{
id: 'concept2',
type: 'concept',
noun: 'concept',
name: 'Vector Search',
content: 'Technique for finding similar items in large datasets using vector representations.',
tags: ['search', 'vectors']
},
// Group nouns
{
id: 'group1',
type: 'group',
noun: 'group',
name: 'Research Team',
content: 'Team of researchers working on cutting-edge AI technologies.',
tags: ['team', 'research']
},
// Content nouns
{
id: 'content1',
type: 'document',
noun: 'content',
title: 'Introduction to Machine Learning',
content: 'Machine learning is a subset of artificial intelligence that focuses on algorithms.',
tags: ['AI', 'ML', 'algorithms']
},
{
id: 'content2',
type: 'document',
noun: 'content',
title: 'Graph Databases Explained',
content: 'Graph databases store data in graph structures with nouns, verbs, and properties.',
tags: ['databases', 'graphs', 'NoSQL']
}
]
// Add Nouns to the database
for (const item of sampleNouns) {
// Pass the content as the data to vectorize, and the entire item as metadata
await database.add(item.content || item.name, item)
}
// Wait a moment to ensure nouns are fully indexed before adding verbs
await new Promise(resolve => setTimeout(resolve, 500))
// Verify nouns exist before adding verbs
const entities = await database.getAllNouns()
const nounIds = entities.map(entity => entity.id)
console.log('Available nouns before adding verbs:', nounIds)
// Sample Verbs with various types from the schema
const sampleVerbs = [
// Created verbs
{
source: 'person1',
target: 'content1',
verb: 'created',
label: 'Created',
data: { strength: 0.9, description: 'John Smith authored the introduction to machine learning' }
},
{
source: 'person2',
target: 'content2',
verb: 'created',
label: 'Created',
data: { strength: 0.85, description: 'Emily Johnson wrote the graph databases explanation' }
},
// WorksWith verbs
{
source: 'person1',
target: 'person2',
verb: 'worksWith',
label: 'Works With',
data: { strength: 0.7, description: 'John and Emily collaborate on research projects' }
},
{
source: 'person2',
target: 'person3',
verb: 'worksWith',
label: 'Works With',
data: { strength: 0.65, description: 'Emily and Michael work together on product development' }
},
// MemberOf verbs
{
source: 'person1',
target: 'group1',
verb: 'memberOf',
label: 'Member Of',
data: { strength: 0.8, description: 'John is a member of the research team' }
},
{
source: 'person2',
target: 'group1',
verb: 'memberOf',
label: 'Member Of',
data: { strength: 0.8, description: 'Emily is a member of the research team' }
},
// RelatedTo verbs
{
source: 'concept1',
target: 'concept2',
verb: 'relatedTo',
label: 'Related To',
data: { strength: 0.75, description: 'Machine learning is related to vector search' }
},
{
source: 'content1',
target: 'content2',
verb: 'relatedTo',
label: 'Related To',
data: { strength: 0.6, description: 'Machine learning content is related to graph databases content' }
},
// Owns verbs
{
source: 'person3',
target: 'thing2',
verb: 'owns',
label: 'Owns',
data: { strength: 0.9, description: 'Michael owns the graph database product' }
},
// FriendOf verbs
{
source: 'person1',
target: 'person3',
verb: 'friendOf',
label: 'Friend Of',
data: { strength: 0.5, description: 'John and Michael are friends outside of work' }
},
// Mentors verbs
{
source: 'person2',
target: 'person1',
verb: 'mentors',
label: 'Mentors',
data: { strength: 0.85, description: 'Emily mentors John on data science techniques' }
},
// AttributedTo verbs
{
source: 'thing1',
target: 'person2',
verb: 'attributedTo',
label: 'Attributed To',
data: { strength: 0.7, description: 'The neural network implementation is attributed to Emily' }
}
]
// Add Verbs to the database
for (const item of sampleVerbs) {
try {
// Check if source and target nouns exist before adding the verb
if (!nounIds.includes(item.source)) {
throw new Error(`Source noun with ID ${item.source} not found. Available nouns: ${nounIds.join(', ')}`)
}
if (!nounIds.includes(item.target)) {
throw new Error(`Target noun with ID ${item.target} not found. Available nouns: ${nounIds.join(', ')}`)
}
await database.relate(item.source, item.target, item.verb, item.data)
console.log(`Added verb: ${item.source} ${item.verb} ${item.target}`)
} catch (verbError) {
console.error(`Failed to add verb: ${verbError.message}`)
log('db-status', `Failed to add verb: ${verbError.message}`)
}
}
// Count successfully added verbs
const addedVerbs = await database.getAllVerbs()
log('db-status', `Added ${sampleNouns.length} nouns and ${addedVerbs.length} verbs`)
updateDatabaseVisualization()
// Populate noun dropdowns after adding sample data
populateNounDropdowns()
} catch (error) {
log('db-status', 'Error adding sample data: ' + error.message)
}
}
async function clearDatabase() {
try {
if (database) {
await database.clear()
log('db-status', 'Database cleared successfully')
updateDatabaseVisualization()
populateNounDropdowns()
}
} catch (error) {
log('db-status', 'Error clearing database: ' + error.message)
}
}
async function backupData() {
if (!database) {
log('db-status', 'No database to backup')
return
}
try {
const data = await database.backup()
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'brainy-backup.json'
a.click()
URL.revokeObjectURL(url)
log('db-status', 'Data backed up successfully')
} catch (error) {
log('db-status', 'Error backing up data: ' + error.message)
}
}
async function restoreData() {
const input = document.createElement('input')
input.type = 'file'
input.accept = '.json'
input.onchange = async function(e) {
const file = e.target.files[0]
if (!file) return
try {
const text = await file.text()
const data = JSON.parse(text)
if (database) {
await database.restore(data)
log('db-status', 'Data restored successfully')
updateDatabaseVisualization()
// Show information about the restored data
let restoreInfo = `Restored ${data.nouns?.length || 0} nouns and ${data.verbs?.length || 0} verbs.`
// Check if the backup included vectors and index
const hasVectors = data.nouns?.some(n => n.vector && n.vector.length > 0)
const hasIndex = !!data.hnswIndex
if (!hasVectors) {
restoreInfo += ' Vectors were automatically created during restore.'
}
if (!hasIndex) {
restoreInfo += ' HNSW index was reconstructed during restore.'
}
log('db-status', restoreInfo)
}
} catch (error) {
log('db-status', 'Error restoring data: ' + error.message)
}
}
input.click()
}
// Function to create and restore sparse data (without vectors)
async function restoreSparseData() {
if (!database) {
log('db-status', 'Please initialize database first')
return
}
try {
// Create sparse data without vectors
const sparseData = {
nouns: [
{
id: 'sparse_1',
metadata: {
noun: 'Thing',
text: 'This is a sparse data item without a vector'
}
},
{
id: 'sparse_2',
metadata: {
noun: 'Concept',
text: 'Vectors will be automatically created for this item'
}
}
],
verbs: [
{
id: 'sparse_verb_1',
sourceId: 'sparse_1',
targetId: 'sparse_2',
metadata: {
verb: 'RelatedTo',
description: 'This is a relationship between sparse data items'
}
}
],
version: '1.0.0'
}
// Restore the sparse data
await database.restore(sparseData)
log('db-status', 'Sparse data restored successfully')
log('db-status', 'Vectors were automatically created for items without vectors')
updateDatabaseVisualization()
} catch (error) {
log('db-status', 'Error restoring sparse data: ' + error.message)
}
}
// Entity operations
async function addEntity() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
const type = document.getElementById('entity-type').value
const dataText = document.getElementById('entity-data').value
if (!dataText.trim()) {
log('entity-results', 'Please enter entity data')
return
}
const data = JSON.parse(dataText)
data.type = type
data.id = data.id || 'entity_' + Date.now()
// Use a text property for vectorization, or stringify the object if no text property exists
const textToVectorize = data.content || data.description || data.text ||
data.title || data.name || JSON.stringify(data)
await database.add(textToVectorize, data)
log('entity-results', `Added ${type} entity: ${data.id}`)
// Clear the form
document.getElementById('entity-data').value = ''
} catch (error) {
log('entity-results', 'Error adding entity: ' + error.message)
}
}
async function createRelationship() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
// For demo purposes, create a relationship between first two entities
const entities = await database.search('', 2, { forceEmbed: false })
if (entities.length < 2) {
log('entity-results', 'Need at least 2 entities to create a relationship')
return
}
await database.relate(entities[0].id, entities[1].id, 'connected_to', {
created: new Date().toISOString(),
strength: Math.random()
})
log('entity-results', `Created relationship: ${entities[0].id} -> ${entities[1].id}`)
} catch (error) {
log('entity-results', 'Error creating relationship: ' + error.message)
}
}
async function queryEntities() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
const entities = await database.search('', 1000, { forceEmbed: false })
const relationships = await database.getAllVerbs()
let result = `Found ${entities.length} entities and ${relationships.length} relationships\n\n`
result += 'Entities:\n'
entities.forEach(entity => {
result += `- ${entity.id} (${entity.type}): ${entity.title || entity.name || 'Untitled'}\n`
})
result += '\nRelationships:\n'
relationships.forEach(rel => {
result += `- ${rel.sourceId || rel.from} ${rel.type} ${rel.targetId || rel.to}\n`
})
log('entity-results', result)
} catch (error) {
log('entity-results', 'Error querying entities: ' + error.message)
}
}
// Noun operations
async function addNoun() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
const nounType = document.getElementById('noun-type').value
const nounId = document.getElementById('noun-id').value.trim()
const nounName = document.getElementById('noun-name').value.trim()
const nounContent = document.getElementById('noun-content').value.trim()
const nounTags = document.getElementById('noun-tags').value.trim()
if (!nounName) {
log('entity-results', 'Please enter a name for the noun')
return
}
// Create data object from form fields
const data = {
noun: nounType,
id: nounId || 'noun_' + Date.now(),
name: nounName,
content: nounContent
}
// Add tags if provided
if (nounTags) {
data.tags = nounTags.split(',').map(tag => tag.trim())
}
// Use content for vectorization, or name if content is empty
const textToVectorize = nounContent || nounName
await database.add(textToVectorize, data)
log('entity-results', `Added ${nounType} noun: ${data.id}`)
// Clear the form
document.getElementById('noun-id').value = ''
document.getElementById('noun-name').value = ''
document.getElementById('noun-content').value = ''
document.getElementById('noun-tags').value = ''
// Update visualization
updateDatabaseVisualization()
// Update noun dropdowns with the new noun
populateNounDropdowns()
} catch (error) {
log('entity-results', 'Error adding noun: ' + error.message)
}
}
async function queryNouns() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
// Query all entities that have a 'noun' property
const entities = await database.search('', 1000, { forceEmbed: false })
const nouns = entities.filter(entity => entity.noun)
let result = `Found ${nouns.length} nouns\n\n`
result += 'Nouns:\n'
nouns.forEach(noun => {
result += `- ${noun.id} (${noun.noun}): ${noun.title || noun.name || 'Untitled'}\n`
})
log('entity-results', result)
} catch (error) {
log('entity-results', 'Error querying nouns: ' + error.message)
}
}
// Verb operations
async function addVerb() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
const verbType = document.getElementById('verb-type').value
const sourceId = document.getElementById('source-id').value.trim()
const targetId = document.getElementById('target-id').value.trim()
const verbStrength = document.getElementById('verb-strength').value
const verbDescription = document.getElementById('verb-description').value.trim()
if (!sourceId || !targetId) {
log('entity-results', 'Please enter source and target node IDs')
return
}
// Create data object from form fields
const data = {
strength: parseFloat(verbStrength) || 0.8,
description: verbDescription
}
// Verify nouns exist before adding verb
const entities = await database.search('', 1000, { forceEmbed: false })
const nounIds = entities.map(entity => entity.id)
// Check if source and target nouns exist
if (!nounIds.includes(sourceId)) {
throw new Error(`Source noun with ID ${sourceId} not found. Available nouns: ${nounIds.join(', ')}`)
}
if (!nounIds.includes(targetId)) {
throw new Error(`Target noun with ID ${targetId} not found. Available nouns: ${nounIds.join(', ')}`)
}
// Add the relationship
await database.relate(sourceId, targetId, verbType, data)
console.log(`Added verb: ${sourceId} ${verbType} ${targetId}`)
log('entity-results', `Added ${verbType} verb: ${sourceId} -> ${targetId}`)
// Clear the form
document.getElementById('source-id').value = ''
document.getElementById('target-id').value = ''
document.getElementById('verb-strength').value = '0.8'
document.getElementById('verb-description').value = ''
// Update visualization
updateDatabaseVisualization()
} catch (error) {
log('entity-results', 'Error adding verb: ' + error.message)
}
}
async function queryVerbs() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
const relationships = await database.getAllVerbs()
let result = `Found ${relationships.length} verbs (relationships)\n\n`
result += 'Verbs:\n'
relationships.forEach(rel => {
result += `- ${rel.sourceId || rel.from} ${rel.type} ${rel.targetId || rel.to}\n`
})
log('entity-results', result)
} catch (error) {
log('entity-results', 'Error querying verbs: ' + error.message)
}
}
// Populate noun dropdowns for verb creation
async function populateNounDropdowns() {
if (!database) {
log('entity-results', 'Please initialize database first')
return
}
try {
// Get all nouns from the database
const entities = await database.search('', 1000, { forceEmbed: false })
const nouns = entities.filter(entity => entity.noun)
// Get the dropdown elements
const sourceDropdown = document.getElementById('source-id')
const targetDropdown = document.getElementById('target-id')
// Clear existing options except the first one
while (sourceDropdown.options.length > 1) {
sourceDropdown.remove(1)
}
while (targetDropdown.options.length > 1) {
targetDropdown.remove(1)
}
// Add nouns to the dropdowns
nouns.forEach(noun => {
const displayName = noun.name || noun.title || noun.id
const option1 = document.createElement('option')
option1.value = noun.id
option1.text = `${displayName} (${noun.noun}, ID: ${noun.id})`
sourceDropdown.add(option1)
const option2 = document.createElement('option')
option2.value = noun.id
option2.text = `${displayName} (${noun.noun}, ID: ${noun.id})`
targetDropdown.add(option2)
})
log('entity-results', `Refreshed noun dropdowns with ${nouns.length} nouns`)
} catch (error) {
log('entity-results', 'Error populating noun dropdowns: ' + error.message)
}
}
// Search operations
async function performSearch() {
if (!database) {
log('search-results', 'Please initialize database first')
return
}
try {
const query = document.getElementById('search-query').value
const searchType = document.getElementById('search-type').value
if (!query.trim()) {
log('search-results', 'Please enter a search query')
return
}
log('search-results', `Searching for: "${query}" (${searchType})`)
let results
switch (searchType) {
case 'semantic':
results = await database.search(query, { type: 'semantic', limit: 10 })
break
case 'keyword':
results = await database.search(query, { type: 'keyword', limit: 10 })
break
case 'hybrid':
results = await database.search(query, { type: 'hybrid', limit: 10 })
break
}
let output = `Found ${results.length} results:\n\n`
results.forEach((result, index) => {
output += `${index + 1}. ${result.title || result.id} (Score: ${result.score?.toFixed(4) || 'N/A'})\n`
if (result.content) {
output += ` ${result.content.substring(0, 100)}...\n`
}
output += '\n'
})
log('search-results', output)
updateSearchVisualization(results)
} catch (error) {
log('search-results', 'Error performing search: ' + error.message)
}
}
async function findSimilarEntities() {
if (!database) {
log('search-results', 'Please initialize database first')
return
}
try {
const entities = await database.search('', 1, { forceEmbed: false })
if (entities.length === 0) {
log('search-results', 'No entities found to compare similarity')
return
}
const similar = await database.findSimilar(entities[0].id, { limit: 5 })
let output = `Similar entities to "${entities[0].title || entities[0].id}":\n\n`
similar.forEach((entity, index) => {
output += `${index + 1}. ${entity.title || entity.id} (Similarity: ${entity.similarity?.toFixed(4) || 'N/A'})\n`
})
log('search-results', output)
} catch (error) {
log('search-results', 'Error finding similar entities: ' + error.message)
}
}
async function performClusterAnalysis() {
if (!database) {
log('search-results', 'Please initialize database first')
return
}
try {
log('search-results', 'Performing cluster analysis...')
// Simulate cluster analysis
const entities = await database.search('', 1000, { forceEmbed: false })
const clusters = Math.min(3, Math.ceil(entities.length / 2))
let output = `Cluster Analysis Results:\n\n`
output += `Total entities: ${entities.length}\n`
output += `Suggested clusters: ${clusters}\n\n`
// Group entities into clusters (simplified simulation)
for (let i = 0; i < clusters; i++) {
const clusterEntities = entities.filter((_, index) => index % clusters === i)
output += `Cluster ${i + 1} (${clusterEntities.length} entities):\n`
clusterEntities.forEach(entity => {
output += ` - ${entity.title || entity.id}\n`
})
output += '\n'
}
log('search-results', output)
} catch (error) {
log('search-results', 'Error performing cluster analysis: ' + error.message)
}
}
// Augmentation operations removed
// Visualization functions
function setupVisualizations() {
// Initialize D3 visualizations
updateDatabaseVisualization()
}
function showVisualization(type) {
const container = document.getElementById('main-visualization')
container.innerHTML = `<div style="text-align: center; padding-top: 200px; color: #666;">
${type.charAt(0).toUpperCase() + type.slice(1)} visualization would appear here
</div>`
// Simulate different visualization types
switch (type) {
case 'graph':
createGraphVisualization(container)
break
case 'vector':
createVectorSpaceVisualization(container)
break
case 'timeline':
createTimelineVisualization(container)
break
case 'heatmap':
createHeatmapVisualization(container)
break
}
}
function createGraphVisualization(container) {
// Create a graph visualization with D3 that shows both Nouns and Verbs
const width = container.clientWidth
const height = container.clientHeight
container.innerHTML = ''
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height)
// Sample graph data with explicit Nouns and Verbs
const nouns = [
{
id: 'doc1',
type: 'document',
noun: 'content',
title: 'Introduction to Machine Learning',
x: width / 3,
y: height / 3
},
{
id: 'doc2',
type: 'document',
noun: 'content',
title: 'Graph Databases Explained',
x: 2 * width / 3,
y: height / 3
},
{
id: 'doc3',
type: 'document',
noun: 'content',
title: 'Vector Search in Modern Applications',
x: width / 2,
y: 2 * height / 3
}
]
const verbs = [
{
id: 'rel1',
source: 'doc1',
target: 'doc3',
verb: 'relatedTo',
label: 'Related To',
data: { strength: 0.8 }
},
{
id: 'rel2',
source: 'doc2',
target: 'doc3',
verb: 'mentions',
label: 'Mentions',
data: { strength: 0.6 }
}
]
// Create a legend
const legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${width - 150}, 20)`)
// Noun legend
legend.append('circle')
.attr('cx', 10)
.attr('cy', 10)
.attr('r', 8)
.attr('fill', '#4CAF50')
legend.append('text')
.attr('x', 25)
.attr('y', 15)
.text('Nouns')
.attr('font-size', '12px')
// Verb legend
legend.append('line')
.attr('x1', 0)
.attr('y1', 40)
.attr('x2', 20)
.attr('y2', 40)
.attr('stroke', '#2196F3')
.attr('stroke-width', 3)
legend.append('text')
.attr('x', 25)
.attr('y', 45)
.text('Verbs')
.attr('font-size', '12px')
// Create arrow marker for directed edges
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 20)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#2196F3')
// Draw verbs (edges)
const links = svg.selectAll('.link')
.data(verbs)
.enter()
.append('g')
.attr('class', 'link')
links.append('line')
.attr('x1', d => nouns.find(n => n.id === d.source).x)
.attr('y1', d => nouns.find(n => n.id === d.source).y)
.attr('x2', d => nouns.find(n => n.id === d.target).x)
.attr('y2', d => nouns.find(n => n.id === d.target).y)
.attr('stroke', '#2196F3')
.attr('stroke-width', d => 1 + (d.data?.strength || 0.5) * 3)
.attr('marker-end', 'url(#arrowhead)')
// Add verb labels
links.append('text')
.attr('x', d => (nouns.find(n => n.id === d.source).x + nouns.find(n => n.id === d.target).x) / 2)
.attr('y', d => (nouns.find(n => n.id === d.source).y + nouns.find(n => n.id === d.target).y) / 2 - 10)
.attr('text-anchor', 'middle')
.attr('fill', '#2196F3')
.attr('font-size', '10px')
.attr('font-weight', 'bold')
.text(d => d.label || d.verb)
// Draw nouns (nodes)
const nodes = svg.selectAll('.node')
.data(nouns)
.enter()
.append('g')
.attr('class', 'node')
.attr('transform', d => `translate(${d.x}, ${d.y})`)
nodes.append('circle')
.attr('r', 25)
.attr('fill', '#4CAF50')
.attr('stroke', '#388E3C')
.attr('stroke-width', 2)
// Add noun labels
nodes.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 5)
.attr('fill', 'white')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text(d => d.id)
// Add noun type labels
nodes.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 40)
.attr('fill', '#333')
.attr('font-size', '10px')
.text(d => d.noun || d.type)
}
function createVectorSpaceVisualization(container) {
const width = container.clientWidth
const height = container.clientHeight
container.innerHTML = ''
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height)
// Generate random points in 2D space
const points = Array.from({ length: 20 }, (_, i) => ({
id: `point${i}`,
x: Math.random() * (width - 40) + 20,
y: Math.random() * (height - 40) + 20,
cluster: Math.floor(Math.random() * 3)
}))
const colors = ['#4CAF50', '#2196F3', '#FF9800']
svg.selectAll('circle')
.data(points)
.enter()
.append('circle')
.attr('cx', d => d.x)
.attr('cy', d => d.y)
.attr('r', 5)
.attr('fill', d => colors[d.cluster])
.attr('opacity', 0.7)
}
function createTimelineVisualization(container) {
container.innerHTML = `
<div style="padding: 20px;">
<h4>Timeline Visualization</h4>
<div style="height: 400px; background: linear-gradient(to right, #e3f2fd, #bbdefb);
border-radius: 8px; padding: 20px; position: relative;">
<div style="position: absolute; left: 50px; top: 50px; width: 200px; height: 30px;
background: #4CAF50; border-radius: 4px; color: white;
display: flex; align-items: center; justify-content: center;">
Database Init
</div>
<div style="position: absolute; left: 300px; top: 120px; width: 200px; height: 30px;
background: #2196F3; border-radius: 4px; color: white;
display: flex; align-items: center; justify-content: center;">
Data Added
</div>
<div style="position: absolute; left: 150px; top: 190px; width: 200px; height: 30px;
background: #FF9800; border-radius: 4px; color: white;
display: flex; align-items: center; justify-content: center;">
Search Query
</div>
</div>
</div>
`
}
function createHeatmapVisualization(container) {
const width = container.clientWidth
const height = container.clientHeight
container.innerHTML = ''
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height)
const cellSize = 30
const rows = Math.floor(height / cellSize)
const cols = Math.floor(width / cellSize)
const colorScale = d3.scaleSequential(d3.interpolateRdYlBu)
.domain([0, 1])
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
const similarity = Math.random()
svg.append('rect')
.attr('x', j * cellSize)
.attr('y', i * cellSize)
.attr('width', cellSize - 1)
.attr('height', cellSize - 1)
.attr('fill', colorScale(similarity))
.attr('opacity', 0.8)
}
}
}
// Helper function to create demo entities for visualization
function createDemoEntities() {
return [
// Person nouns
{
id: 'demo-person1',
type: 'person',
noun: 'person',
name: 'John Smith',
content: 'Software engineer with expertise in AI and machine learning.',
tags: ['engineer', 'developer'],
metadata: { type: 'person', noun: 'person' }
},
{
id: 'demo-person2',
type: 'person',
noun: 'person',
name: 'Emily Johnson',
content: 'Data scientist specializing in natural language processing.',
tags: ['scientist', 'researcher'],
metadata: { type: 'person', noun: 'person' }
},
// Thing nouns
{
id: 'demo-thing1',
type: 'thing',
noun: 'thing',
name: 'Neural Network',
content: 'Computational model inspired by the human brain.',
tags: ['AI', 'computing'],
metadata: { type: 'thing', noun: 'thing' }
},
{
id: 'demo-thing2',
type: 'thing',
noun: 'thing',
name: 'Graph Database',
content: 'Database that uses graph structures for semantic queries.',
tags: ['database', 'storage'],
metadata: { type: 'thing', noun: 'thing' }
},
// Concept nouns
{
id: 'demo-concept1',
type: 'concept',
noun: 'concept',
name: 'Machine Learning',
content: 'Field of study that gives computers the ability to learn without being explicitly programmed.',
tags: ['AI', 'algorithms'],
metadata: { type: 'concept', noun: 'concept' }
}
]
}
// Helper function to create demo relationships for visualization
function createDemoRelationships() {
return [
// Created verbs
{
id: 'demo-verb1',
source: 'demo-person1',
target: 'demo-thing1',
verb: 'created',
label: 'Created',
data: { strength: 0.9, description: 'John Smith created the neural network' }
},
// WorksWith verbs
{
id: 'demo-verb2',
source: 'demo-person1',
target: 'demo-person2',
verb: 'worksWith',
label: 'Works With',
data: { strength: 0.7, description: 'John and Emily collaborate on research projects' }
},
// RelatedTo verbs
{
id: 'demo-verb3',
source: 'demo-thing1',
target: 'demo-concept1',
verb: 'relatedTo',
label: 'Related To',
data: { strength: 0.75, description: 'Neural networks are related to machine learning' }
}
]
}
// Function to create a graph visualization with the provided data
function createGraphWithData(g, entities, relationships, width, height, zoom, svg) {
// Create a legend (outside the zoom group to keep it fixed)
const legend = svg.append('g')
.attr('class', 'legend')
.attr('transform', `translate(${width - 150}, 20)`)
// Noun legend
legend.append('circle')
.attr('cx', 10)
.attr('cy', 10)
.attr('r', 8)
.attr('fill', '#4CAF50')
legend.append('text')
.attr('x', 25)
.attr('y', 15)
.text('Nouns')
.attr('font-size', '12px')
// Verb legend
legend.append('line')
.attr('x1', 0)
.attr('y1', 40)
.attr('x2', 20)
.attr('y2', 40)
.attr('stroke', '#2196F3')
.attr('stroke-width', 3)
legend.append('text')
.attr('x', 25)
.attr('y', 45)
.text('Verbs')
.attr('font-size', '12px')
// Create a force simulation
const simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(d => d.id).distance(200))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(80))
// Prepare the data for D3
const nodes = entities.map(entity => ({
id: entity.id,
type: entity.type || 'unknown',
noun: entity.noun || entity.type || 'unknown',
title: entity.title || entity.name || entity.id
}))
// Filter relationships to only include those where both source and target nodes exist
const links = relationships
.filter(rel => {
// Use sourceId and targetId (GraphVerb properties) or fallback to from/to or source/target
const sourceId = rel.sourceId || rel.from || rel.source
const targetId = rel.targetId || rel.to || rel.target
// Check if both source and target nodes exist in the nodes array
const sourceExists = nodes.some(node => node.id === sourceId)
const targetExists = nodes.some(node => node.id === targetId)
return sourceExists && targetExists
})
.map(rel => ({
source: rel.sourceId || rel.from || rel.source,
target: rel.targetId || rel.to || rel.target,
verb: rel.type || rel.verb,
label: (rel.type || rel.verb || '').charAt(0).toUpperCase() + (rel.type || rel.verb || '').slice(1).replace(/([A-Z])/g, ' $1'),
data: rel.data || {}
}))
// Create arrow marker for directed edges
svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 25)
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 6)
.attr('markerHeight', 6)
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#2196F3')
// Draw verbs (edges)
const link = g.selectAll('.link')
.data(links)
.enter()
.append('g')
.attr('class', 'link')
const linkLine = link.append('line')
.attr('stroke', '#2196F3')
.attr('stroke-width', d => 1 + (d.data?.strength || 0.5) * 3)
.attr('marker-end', 'url(#arrowhead)')
// Add verb labels
const linkText = link.append('text')
.attr('text-anchor', 'middle')
.attr('fill', '#2196F3')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.attr('background', 'white')
.attr('paint-order', 'stroke')
.attr('stroke', 'white')
.attr('stroke-width', '3px')
.text(d => d.label || d.verb)
// Draw nouns (nodes)
const node = g.selectAll('.node')
.data(nodes)
.enter()
.append('g')
.attr('class', 'node')
.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended))
node.append('circle')
.attr('r', 25)
.attr('fill', d => {
// Color by type
switch (d.type) {
case 'person':
return '#4CAF50'
case 'place':
return '#FF9800'
case 'thing':
return '#2196F3'
case 'event':
return '#F44336'
case 'concept':
return '#9C27B0'
case 'group':
return '#795548'
case 'document':
return '#607D8B'
default:
return '#BDBDBD'
}
})
.attr('stroke', '#388E3C')
.attr('stroke-width', 2)
// Add noun labels
node.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 5)
.attr('fill', 'white')
.attr('font-size', '12px')
.attr('font-weight', 'bold')
.text(d => d.title)
// Add noun type labels
node.append('text')
.attr('text-anchor', 'middle')
.attr('dy', 40)
.attr('fill', '#333')
.attr('font-size', '10px')
.text(d => d.noun || d.type)
// Update positions on simulation tick
simulation.nodes(nodes).on('tick', ticked)
simulation.force('link').links(links)
// Function to fit the graph to the container
function zoomToFit() {
if (!nodes.length) return
// Calculate the bounds of the graph
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
nodes.forEach(node => {
if (node.x < minX) minX = node.x
if (node.y < minY) minY = node.y
if (node.x > maxX) maxX = node.x
if (node.y > maxY) maxY = node.y
})
// Add padding - increased to give more room for labels
const padding = 100
minX -= padding
minY -= padding
maxX += padding
maxY += padding
// Calculate scale and translate to fit the graph
const scale = Math.min(
width / (maxX - minX),
height / (maxY - minY)
)
const translate = [
width / 2 - scale * (minX + maxX) / 2,
height / 2 - scale * (minY + maxY) / 2
]
// Apply the zoom transform
svg.transition().duration(750).call(
zoom.transform,
d3.zoomIdentity
.translate(translate[0], translate[1])
.scale(scale)
)
}
// Call zoomToFit after the simulation has stabilized
simulation.on('end', zoomToFit)
// Also call zoomToFit after a timeout to ensure it happens even if simulation doesn't end
setTimeout(zoomToFit, 1000)
function ticked() {
linkLine
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y)
linkText
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2 - 15)
// Add a slight offset to prevent overlap with the line
linkText.each(function(d) {
const dx = d.target.x - d.source.x
const dy = d.target.y - d.source.y
const angle = Math.atan2(dy, dx)
// Calculate perpendicular offset
const perpX = -Math.sin(angle) * 8
const perpY = Math.cos(angle) * 8
// Apply the offset
d3.select(this)
.attr('x', (d.source.x + d.target.x) / 2 + perpX)
.attr('y', (d.source.y + d.target.y) / 2 - 15 + perpY)
})
node
.attr('transform', d => `translate(${d.x}, ${d.y})`)
}
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
}
function dragged(event, d) {
d.fx = event.x
d.fy = event.y
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
}
async function updateDatabaseVisualization() {
const visualizationContainer = document.getElementById('db-visualization')
const graphContainer = visualizationContainer.querySelector('.graph-container')
if (!database) {
graphContainer.innerHTML = '<div style="text-align: center; padding-top: 200px; color: #666;">Database not initialized</div>'
return
}
try {
// Query all entities and relationships from the database
const entities = await database.search('', 1000, { forceEmbed: false })
const relationships = await database.getAllVerbs()
// Clear the container
graphContainer.innerHTML = ''
// If the database is empty, use demo data for visualization
let nodes = []
let edges = []
if (entities.length === 0) {
// Create demo data for visualization purposes only
const demoEntities = createDemoEntities()
const demoRelationships = createDemoRelationships()
// Show message that we're using demo data
const demoMessage = document.createElement('div')
demoMessage.style.position = 'absolute'
demoMessage.style.bottom = '10px'
demoMessage.style.left = '10px'
demoMessage.style.backgroundColor = 'rgba(255, 255, 255, 0.8)'
demoMessage.style.padding = '5px 10px'
demoMessage.style.borderRadius = '5px'
demoMessage.style.fontSize = '12px'
demoMessage.style.color = '#666'
demoMessage.textContent = 'Using demo data for visualization. Add real data to see your own graph.'
visualizationContainer.appendChild(demoMessage)
// Transform demo data to the format expected by the visualization
nodes = demoEntities.map(entity => ({
id: entity.id,
noun: entity.type || 'concept',
data: {
displayName: entity.name || entity.title || entity.id,
handle: entity.id,
avatar: '',
...entity
}
}))
edges = demoRelationships.map(rel => ({
source: rel.source || rel.from || rel.sourceId,
target: rel.target || rel.to || rel.targetId,
verb: rel.verb || rel.type || 'relatedTo',
confidence: 0.8,
data: rel.data || {}
}))
} else {
// Transform real data to the format expected by the visualization
nodes = entities.map(entity => ({
id: entity.id,
noun: entity.noun || entity.type || 'concept',
data: {
displayName: entity.name || entity.title || entity.id,
handle: entity.id,
avatar: '',
...entity
}
}))
edges = relationships.map(rel => ({
source: rel.sourceId || rel.from || rel.source,
target: rel.targetId || rel.to || rel.target,
verb: rel.verb || rel.type || 'relatedTo',
confidence: 0.8,
data: rel.data || {}
}))
}
// Ensure all nodes referenced in edges exist in the nodes array
const nodeIds = new Set(nodes.map(node => node.id))
const validEdges = edges.filter(edge => {
const sourceId = typeof edge.source === 'string' ? edge.source : edge.source.id
const targetId = typeof edge.target === 'string' ? edge.target : edge.target.id
return nodeIds.has(sourceId) && nodeIds.has(targetId)
})
// Log any filtered edges for debugging
if (validEdges.length < edges.length) {
console.warn(`Filtered out ${edges.length - validEdges.length} edges with missing nodes`)
edges.forEach(edge => {
const sourceId = typeof edge.source === 'string' ? edge.source : edge.source.id
const targetId = typeof edge.target === 'string' ? edge.target : edge.target.id
if (!nodeIds.has(sourceId)) {
console.warn(`Edge references missing source node: ${sourceId}`)
}
if (!nodeIds.has(targetId)) {
console.warn(`Edge references missing target node: ${targetId}`)
}
})
}
// Create the cartographer-style visualization
createCartographerGraph(graphContainer, nodes, validEdges)
// Set up zoom-fit button
const zoomFitBtn = document.getElementById('zoom-fit-btn')
zoomFitBtn.addEventListener('click', () => {
zoomToFit()
})
// Auto zoom to fit the data view
setTimeout(() => {
zoomToFit()
}, 100)
} catch (error) {
console.error('Error updating visualization:', error)
graphContainer.innerHTML = `<div style="text-align: center; padding-top: 200px; color: #666;">Error updating visualization: ${error.message}</div>`
}
}
// Global variables for the graph
let svgElement = null
let zoomBehavior = null
let graphContentContainer = null
function createCartographerGraph(container, nodes, edges) {
// Remove any existing SVG
d3.select(container).select('svg').remove()
const width = container.clientWidth
const height = container.clientHeight
// Material Design 3 Inspired Colors
const md3Colors = {
primary: '#6750A4',
onPrimary: '#FFFFFF',
secondaryContainer: '#E8DEF8',
onSecondaryContainer: '#1D192B',
outline: '#79747E',
surface: '#FFFBFE',
shadow: 'rgba(0, 0, 0, 0.3)'
}
// Palette for nodes
const nodeColorPalette = [
md3Colors.primary,
'#4A5BF5',
'#278853',
'#A83A57',
'#D56A2C',
'#5D517F'
]
// Create SVG element
const svg = d3.select(container)
.append('svg')
.attr('width', width)
.attr('height', height)
.style('background-color', '#F7F2FA')
// Store reference to SVG element
svgElement = svg
// Create defs for filters
const defs = svg.append('defs')
// Add shadow filter
defs.append('filter')
.attr('id', 'md-shadow')
.attr('x', '-50%')
.attr('y', '-50%')
.attr('width', '200%')
.attr('height', '200%')
.append('feDropShadow')
.attr('dx', '0')
.attr('dy', '4')
.attr('stdDeviation', '6')
.attr('flood-color', md3Colors.shadow)
.attr('flood-opacity', '0.25')
// Create arrow marker for directed edges (from explore view)
defs.append('marker')
.attr('id', 'arrowhead-cartographer')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 22) // Adjusted to match explore view
.attr('refY', 0)
.attr('orient', 'auto')
.attr('markerWidth', 8) // Increased size to match explore view
.attr('markerHeight', 8) // Increased size to match explore view
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#2196F3') // Blue color from explore view
// Create zoom behavior
zoomBehavior = d3.zoom()
.scaleExtent([0.1, 5])
.on('zoom', (event) => {
graphContentContainer.attr('transform', event.transform)
})
svg.call(zoomBehavior)
.on('click', (event) => {
// Only handle clicks directly on the SVG background
if (event.target === svg.node()) {
// Deselect any selected nodes
d3.selectAll('.node-group').transition().duration(300).style('opacity', 1)
d3.selectAll('.edge-group').transition().duration(300).style('opacity', 1)
d3.selectAll('.node-group').classed('selected', false)
}
})
// Create graph container
graphContentContainer = svg.append('g')
.attr('class', 'graph-content-container')
// Create overlay container for popups
const overlayContainer = svg.append('g')
.attr('class', 'overlay-container')
// Create edge groups
const linkGroup = graphContentContainer
.append('g')
.attr('class', 'edges')
.selectAll('g')
.data(edges)
.join('g')
.attr('class', 'edge-group')
// Add lines with thickness based on confidence (using explore view styling)
const link = linkGroup
.append('line')
.attr('stroke', '#2196F3') // Blue color from explore view
.attr('stroke-opacity', 0.8)
.attr('stroke-width', (d) => {
return d.confidence ? 1 + d.confidence * 5 : 1.5
})
.attr('marker-end', 'url(#arrowhead-cartographer)')
// Add edge labels (styled to match explore view)
const labelGroup = linkGroup
.append('g')
.attr('class', 'edge-label-group')
.style('cursor', 'pointer')
// Add background pill for edge labels (matching explore view)
labelGroup
.append('rect')
.attr('class', 'edge-label-background')
.attr('fill', '#E3F2FD') // Light blue background from explore view
.attr('fill-opacity', 0.95)
.attr('stroke', '#2196F3') // Blue outline from explore view
.attr('stroke-width', 1)
.attr('stroke-opacity', 0.8)
.attr('rx', 12) // Rounded corners for pill shape
.attr('ry', 12)
.attr('width', 0) // Will be set dynamically based on text width
.attr('height', 24)
.attr('x', 0)
.attr('y', -12)
.style('filter', 'url(#md-shadow)')
// Add text for edge labels
const edgeText = labelGroup
.append('text')
.attr('class', 'edge-label')
.attr('text-anchor', 'middle')
.attr('fill', '#0D47A1') // Dark blue text from explore view
.attr('dy', '0.35em') // Vertical centering
.style('font-family', '\'Roboto\', \'Inter\', sans-serif')
.style('font-size', '11px')
.style('font-weight', '500')
.style('pointer-events', 'none')
.text(d => {
// Format verb name
const verb = d.verb
return verb.charAt(0).toUpperCase() + verb.slice(1).replace(/([A-Z])/g, ' $1')
})
// Adjust background rect width based on text width
setTimeout(() => {
labelGroup.each(function() {
const text = d3.select(this).select('text')
const textWidth = text.node().getComputedTextLength()
d3.select(this).select('rect')
.attr('width', textWidth + 16) // Add padding
.attr('x', -textWidth / 2 - 8) // Center the rect
})
}, 0)
// Node size for card layout
const nodeSize = { width: 280, height: 80 }
const nodeRadius = 8
// Create node groups
const node = graphContentContainer
.append('g')
.attr('class', 'nodes')
.selectAll('g')
.data(nodes)
.join('g')
.attr('class', 'node-group')
.style('cursor', 'pointer')
// Add card background with different shapes based on node type
node.append('path')
.attr('d', (d) => {
const w = nodeSize.width
const h = nodeSize.height
const r = nodeRadius
const x = -w / 2
const y = -h / 2
// Check if node is a Group or List
if (d.noun === 'group' || d.noun === 'list') {
// Circle for Groups and Lists
const radius = Math.max(w, h) / 1.5
return `
M ${x + w / 2},${y + h / 2 - radius}
a ${radius},${radius} 0 0 1 0,${2 * radius}
a ${radius},${radius} 0 0 1 0,${-2 * radius}
z
`
} else {
// Path with rounded corners for other nodes
return `
M ${x},${y + r}
a ${r},${r} 0 0 1 ${r},${-r}
h ${w - 2 * r}
a ${r},${r} 0 0 1 ${r},${r}
v ${h - 2 * r}
a ${r},${r} 0 0 1 ${-r},${r}
h ${-(w - 2 * r)}
a ${r},${r} 0 0 1 ${-r},${-r}
z
`
}
})
.attr('fill', (d, i) => {
// Assign colors based on node type
const nounType = d.noun || 'concept'
const colorIndex = Math.abs(nounType.charCodeAt(0) + nounType.charCodeAt(nounType.length - 1)) % nodeColorPalette.length
return nodeColorPalette[colorIndex]
})
.attr('fill-opacity', 0.8)
.attr('stroke', md3Colors.outline)
.attr('stroke-width', 1)
.attr('stroke-opacity', 0.3)
.style('filter', 'url(#md-shadow)')
// Add node labels
node.append('text')
.attr('class', 'node-label')
.attr('text-anchor', 'middle')
.attr('fill', 'white')
.attr('y', 0)
.style('font-family', '\'Roboto\', \'Inter\', sans-serif')
.style('font-size', '16px')
.style('font-weight', 'bold')
.style('pointer-events', 'none')
.text(d => {
// Truncate long names
const name = d.data.displayName || d.id
return name.length > 30 ? name.substring(0, 27) + '...' : name
})
// Add node type labels
node.append('text')
.attr('class', 'node-type-label')
.attr('text-anchor', 'middle')
.attr('fill', 'rgba(255, 255, 255, 0.8)')
.attr('y', 20)
.style('font-family', '\'Roboto\', \'Inter\', sans-serif')
.style('font-size', '12px')
.style('font-style', 'italic')
.style('pointer-events', 'none')
.text(d => {
// Format noun type
const nounType = d.noun || 'concept'
return nounType.charAt(0).toUpperCase() + nounType.slice(1)
})
// Add drag behavior
node.call(d3.drag()
.on('start', dragstarted)
.on('drag', dragged)
.on('end', dragended))
// Add click behavior for nodes
node.on('click', (event, d) => {
event.stopPropagation()
// Highlight the selected node and connected edges
const selectedNodeId = d.id
// Dim all nodes and edges
d3.selectAll('.node-group').transition().duration(300).style('opacity', 0.3)
d3.selectAll('.edge-group').transition().duration(300).style('opacity', 0.1)
// Highlight the selected node
d3.selectAll('.node-group').classed('selected', false)
d3.select(event.currentTarget)
.classed('selected', true)
.transition()
.duration(300)
.style('opacity', 1)
// Find connected edges and nodes
const connectedEdges = edges.filter(e =>
e.source === selectedNodeId ||
e.target === selectedNodeId ||
(e.source.id && e.source.id === selectedNodeId) ||
(e.target.id && e.target.id === selectedNodeId)
)
const connectedNodeIds = new Set()
connectedEdges.forEach(e => {
const sourceId = typeof e.source === 'string' ? e.source : e.source.id
const targetId = typeof e.target === 'string' ? e.target : e.target.id
connectedNodeIds.add(sourceId)
connectedNodeIds.add(targetId)
})
// Highlight connected nodes
d3.selectAll('.node-group').each(function(nodeData) {
if (connectedNodeIds.has(nodeData.id)) {
d3.select(this).transition().duration(300).style('opacity', 1)
}
})
// Highlight connected edges
d3.selectAll('.edge-group').each(function(edgeData, i) {
const sourceId = typeof edgeData.source === 'string' ? edgeData.source : edgeData.source.id
const targetId = typeof edgeData.target === 'string' ? edgeData.target : edgeData.target.id
if (sourceId === selectedNodeId || targetId === selectedNodeId) {
d3.select(this).transition().duration(300).style('opacity', 1)
}
})
})
// Create force simulation
const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(edges).id(d => d.id).distance(200))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(100))
.on('tick', ticked)
// Tick function to update positions
function ticked() {
// Update node positions
node.attr('transform', d => `translate(${d.x},${d.y})`)
// Update link positions
link
.attr('x1', d => typeof d.source === 'string' ? d.source.x : d.source.x)
.attr('y1', d => typeof d.source === 'string' ? d.source.y : d.source.y)
.attr('x2', d => typeof d.target === 'string' ? d.target.x : d.target.x)
.attr('y2', d => typeof d.target === 'string' ? d.target.y : d.target.y)
// Update edge label positions (enhanced to match explore view)
labelGroup.attr('transform', d => {
const sourceX = typeof d.source === 'string' ? d.source.x : d.source.x
const sourceY = typeof d.source === 'string' ? d.source.y : d.source.y
const targetX = typeof d.target === 'string' ? d.target.x : d.target.x
const targetY = typeof d.target === 'string' ? d.target.y : d.target.y
// Position label at the midpoint of the edge
const x = (sourceX + targetX) / 2
const y = (sourceY + targetY) / 2
// Calculate angle for rotation (to align with edge direction)
const angle = Math.atan2(targetY - sourceY, targetX - sourceX) * 180 / Math.PI
// Apply a small offset perpendicular to the edge to avoid overlapping
const offsetDistance = 10
const perpX = -Math.sin(angle * Math.PI / 180) * offsetDistance
const perpY = Math.cos(angle * Math.PI / 180) * offsetDistance
// Normalize angle for readability (keep text upright)
const rotationAngle = angle > 90 || angle < -90 ? angle - 180 : angle
return `translate(${x + perpX},${y + perpY}) rotate(${rotationAngle})`
})
}
// Drag functions
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x
d.fy = d.y
}
function dragged(event, d) {
d.fx = event.x
d.fy = event.y
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0)
d.fx = null
d.fy = null
}
// Initial zoom to fit
zoomToFit()
}
function zoomToFit() {
if (!svgElement || !zoomBehavior || !graphContentContainer) return
// Get the bounds of the graph content
const bounds = graphContentContainer.node().getBBox()
const width = svgElement.attr('width')
const height = svgElement.attr('height')
// Add padding to the bounds to ensure labels are visible
const padding = 100
const paddedBounds = {
x: bounds.x - padding,
y: bounds.y - padding,
width: bounds.width + (padding * 2),
height: bounds.height + (padding * 2)
}
// Calculate the scale and translate to fit the graph
const scale = 0.7 * Math.min(
width / paddedBounds.width,
height / paddedBounds.height
)
const translateX = width / 2 - scale * (paddedBounds.x + paddedBounds.width / 2)
const translateY = height / 2 - scale * (paddedBounds.y + paddedBounds.height / 2)
// Apply the zoom transform
svgElement.transition()
.duration(750)
.call(
zoomBehavior.transform,
d3.zoomIdentity
.translate(translateX, translateY)
.scale(scale)
)
}
function updateSearchVisualization(results) {
const container = document.getElementById('search-visualization')
container.innerHTML = `<div style="padding: 20px;">
<h4>Search Results Visualization</h4>
<p>Found ${results.length} matching results</p>
</div>`
}
function resetVisualization() {
showVisualization('graph')
}
function exportVisualization() {
alert('Visualization export functionality would be implemented here')
}
// CLI functions
function executeCLICommand(command) {
const historyElement = document.getElementById('cli-history')
const outputElement = document.getElementById('cli-output')
// Add command to history
const commandElement = document.createElement('div')
commandElement.className = 'cli-command'
commandElement.innerHTML = `<span class="cli-prompt">brainy$</span> ${command}`
historyElement.appendChild(commandElement)
// Simulate command execution
let output = ''
switch (command.split(' ')[0]) {
case 'help':
output = `Available commands:
init Initialize a new database
add <data> Add data to the database
search <q> Search for entities
relate <f> <t> Create relationships
export Export database
status Show database status
clear Clear the terminal
help Show this help message`
break
case 'status':
output = database ?
'Database: Initialized\nStorage: OPFS\nEntities: Available\nAugmentations: Active' :
'Database: Not initialized\nRun "brainy init" to get started'
break
case 'init':
output = 'Initializing database...\nDatabase initialized successfully!'
if (!database) {
initializeDatabase()
}
break
case 'query':
output = database ?
'Querying database...\nFound entities matching your criteria' :
'Error: Database not initialized'
break
default:
output = `Unknown command: ${command}\nType "help" for available commands`
}
outputElement.textContent = output
// Scroll to bottom
const terminal = document.querySelector('.cli-terminal')
terminal.scrollTop = terminal.scrollHeight
}
function clearTerminal() {
document.getElementById('cli-history').innerHTML = ''
document.getElementById('cli-output').textContent = ''
}
// Feature demonstration functions
function demonstrateFeature(feature) {
const resultsContainer = document.getElementById('feature-demo-results')
switch (feature) {
case 'hnsw':
resultsContainer.innerHTML = `
<h4>🔍 HNSW Vector Search Demo</h4>
<p>Hierarchical Navigable Small World (HNSW) is an efficient algorithm for approximate nearest neighbor search.</p>
<div class="code-block">
// Initialize HNSW index
const index = new HNSWIndex(dimension: 384);
// Add vectors
index.addVector(vector1, 'doc1');
index.addVector(vector2, 'doc2');
// Search for similar vectors
const results = index.search(queryVector, k: 10);
</div>
<p><strong>Performance:</strong> O(log N) search time, ideal for large-scale vector databases.</p>
`
break
case 'graph-traversal':
resultsContainer.innerHTML = `
<h4>🌐 Graph Traversal Demo</h4>
<p>Explore relationships and paths through your data graph.</p>
<div class="code-block">
// Find shortest path between entities
const path = await brainy.findPath('entity1', 'entity2');
// Get all neighbors within 2 hops
const neighbors = await brainy.getNeighbors('entity1', { depth: 2 });
// Traverse by relationship type
const related = await brainy.traverse('entity1', 'RELATED_TO');
</div>
<p><strong>Algorithms:</strong> BFS, DFS, shortest path, centrality measures.</p>
`
break
case 'embedding':
resultsContainer.innerHTML = `
<h4>🧠 Embedding Pipeline Demo</h4>
<p>Automatic text-to-vector conversion using pre-trained models.</p>
<div class="code-block">
// Configure embedding pipeline
const pipeline = new EmbeddingPipeline({
model: 'universal-sentence-encoder',
dimension: 512
});
// Process text automatically
const result = await pipeline.process({
text: "Machine learning is transforming technology",
id: "ml-article"
});
// Vector is automatically generated and indexed
console.log(result.vector.length); // 512
</div>
<p><strong>Models:</strong> Universal Sentence Encoder, BERT, custom embeddings.</p>
`
break
case 'realtime':
resultsContainer.innerHTML = `
<h4>📊 Real-time Updates Demo</h4>
<p>Live data synchronization and event streaming.</p>
<div class="code-block">
// Listen for real-time updates
brainy.on('entity:added', (entity) => {
console.log('New entity:', entity.id);
updateVisualization();
});
brainy.on('relationship:created', (relationship) => {
console.log('New relationship:', relationship);
updateGraph();
});
// Subscribe to search result changes
brainy.subscribeToQuery('AI articles', (results) => {
updateSearchResults(results);
});
</div>
<p><strong>Features:</strong> WebSocket support, event streaming, reactive queries.</p>
`
break
case 'storage':
resultsContainer.innerHTML = `
<h4>💾 Storage Backends Demo</h4>
<p>Compare different storage options and their characteristics.</p>
<div class="code-block">
// Memory storage (fastest)
const memoryDB = new BrainyData({
storage: { type: 'memory' }
});
// File system storage (persistent)
const fileDB = new BrainyData({
storage: {
type: 'filesystem',
path: './data'
}
});
// Cloud storage (scalable)
const cloudDB = new BrainyData({
storage: {
type: 's3',
bucket: 'my-brainy-data',
region: 'us-east-1'
}
});
</div>
<p><strong>Options:</strong> Memory, FileSystem, S3, Azure Blob, custom adapters.</p>
`
break
case 'typescript':
resultsContainer.innerHTML = `
<h4>🔧 TypeScript Integration Demo</h4>
<p>Full type safety with comprehensive TypeScript definitions.</p>
<div class="code-block">
// Define your data types
interface Article {
id: string;
title: string;
content: string;
tags: string[];
publishedAt: Date;
}
// Type-safe database operations
const brainy = new BrainyData&lt;Article&gt;();
// TypeScript ensures type safety
const article: Article = {
id: 'article-1',
title: 'AI in 2024',
content: 'The future of AI...',
tags: ['AI', 'technology'],
publishedAt: new Date()
};
// Compile-time type checking
const results = await brainy.search&lt;Article&gt;('AI trends');
</div>
<p><strong>Benefits:</strong> IntelliSense, compile-time errors, better IDE support.</p>
`
break
}
}
// Simulation functions for augmentations
async function simulateTextEmbedding(text) {
await new Promise(resolve => setTimeout(resolve, 1000))
const vector = Array.from({ length: 384 }, () => Math.random() * 2 - 1)
const magnitude = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0))
return `Text Embedding Results:
Input: "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}"
Vector dimension: 384
Magnitude: ${magnitude.toFixed(6)}
Sample values: [${vector.slice(0, 5).map(v => v.toFixed(4)).join(', ')}...]
The text has been successfully converted to a 384-dimensional vector suitable for semantic search and similarity calculations.`
}
async function simulateImageEncoding(content) {
await new Promise(resolve => setTimeout(resolve, 1200))
return `Image Encoding Results:
Input: Image data or URL
Encoding: Base64 with compression
Size reduction: ~75%
Format: Optimized for embedding storage
The image has been encoded and optimized for storage in the vector database.`
}
async function simulateSentimentAnalysis(text) {
await new Promise(resolve => setTimeout(resolve, 800))
const sentiments = ['positive', 'negative', 'neutral']
const sentiment = sentiments[Math.floor(Math.random() * sentiments.length)]
const confidence = Math.random() * 0.5 + 0.5
return `Sentiment Analysis Results:
Input: "${text.substring(0, 100)}${text.length > 100 ? '...' : ''}"
Sentiment: ${sentiment.toUpperCase()}
Confidence: ${(confidence * 100).toFixed(1)}%
Polarity: ${(Math.random() * 2 - 1).toFixed(3)}
The text has been analyzed for emotional tone and sentiment indicators.`
}
async function simulateEntityExtraction(text) {
await new Promise(resolve => setTimeout(resolve, 900))
const entities = [
{ text: 'Machine Learning', type: 'TECHNOLOGY', start: 0, end: 16 },
{ text: 'AI', type: 'TECHNOLOGY', start: 20, end: 22 },
{ text: 'neural networks', type: 'CONCEPT', start: 35, end: 50 }
]
let result = `Entity Extraction Results:
Input: "${text.substring(0, 100)}${text.length > 100 ? '...' : ''}"
Extracted Entities:
`
entities.forEach(entity => {
result += `- ${entity.text} (${entity.type})\n`
})
result += `\nTotal entities found: ${entities.length}`
return result
}
// Utility functions
function log(elementId, message) {
const element = document.getElementById(elementId)
if (element) {
element.textContent = message
element.scrollTop = element.scrollHeight
}
}
function showError(message) {
console.error(message)
const errorDiv = document.createElement('div')
errorDiv.style.cssText = 'background-color: #f44336; color: white; padding: 15px; margin: 10px 0; border-radius: 8px;'
errorDiv.textContent = 'Error: ' + message
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.container').firstChild)
setTimeout(() => {
errorDiv.remove()
}, 5000)
}
</script>
</body>
</html>