brainy/examples/demo.html
David Snelling 7638506ffa Update package scope to @soulcraft and configure as private
Changed package name and imports to use the @soulcraft scope. Updated `README.md` to reflect the use of a private npm package and added publishing/installing instructions. Configured `package.json` with `private: true` and restricted access for publishing. Updated demo link to the correct GitHub repository.
2025-05-23 11:08:02 -07:00

1083 lines
39 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Soulcraft Brainy - Demo</title>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"/>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"/>
<script type="importmap">
{
"imports": {
"uuid": "https://cdn.jsdelivr.net/npm/uuid@9.0.0/dist/esm-browser/index.js",
"@tensorflow/tfjs/dist/tf.esm.js": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.esm.js",
"@tensorflow-models/universal-sentence-encoder/dist/universal-sentence-encoder.esm.js": "https://cdn.jsdelivr.net/npm/@tensorflow-models/universal-sentence-encoder/dist/universal-sentence-encoder.esm.js",
"@tensorflow/tfjs-core": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core/dist/tf-core.esm.js",
"@tensorflow/tfjs-layers": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-layers/dist/tf-layers.esm.js",
"@tensorflow/tfjs-backend-cpu": "https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-cpu/dist/tf-backend-cpu.esm.js"
}
}
</script>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
color: #333;
line-height: 1.6;
}
.navbar {
background-color: #4361ee;
}
.navbar-brand {
font-weight: bold;
color: white !important;
}
.card {
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
border: none;
}
.card-header {
background-color: #4361ee;
color: white;
border-radius: 10px 10px 0 0 !important;
font-weight: 600;
}
pre {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
font-family: 'Courier New', Courier, monospace;
font-size: 14px;
max-height: 300px;
overflow-y: auto;
}
.feature-icon {
font-size: 1.5rem;
margin-right: 10px;
color: #4361ee;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark mb-4">
<div class="container">
<a class="navbar-brand" href="#">
<i class="bi bi-database"></i> Soulcraft Brainy
</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="#">Demo</a>
</li>
<li class="nav-item">
<a
class="nav-link"
href="https://github.com/sodal-project/brainy"
target="_blank"
>GitHub</a
>
</li>
</ul>
</div>
</div>
</nav>
<div class="container">
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h1 class="card-title">
<i class="bi bi-braces"></i> Soulcraft Brainy
</h1>
<p class="card-text lead">
A vector database that runs in a browser or Node.js and utilizes
Origin Private File System (OPFS) for storage, with HNSW
(Hierarchical Navigable Small World) for efficient vector
indexing.
</p>
<div class="d-flex flex-wrap gap-3 mt-4">
<div class="d-flex align-items-center">
<i class="bi bi-globe feature-icon"></i>
<span>Cross-platform</span>
</div>
<div class="d-flex align-items-center">
<i class="bi bi-hdd feature-icon"></i>
<span>Persistent storage</span>
</div>
<div class="d-flex align-items-center">
<i class="bi bi-search feature-icon"></i>
<span>Efficient vector search</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<span>Database Configuration</span>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="distanceFunction" class="form-label"
>Distance Function</label
>
<select class="form-select" id="distanceFunction">
<option value="cosine">Cosine Distance</option>
<option value="euclidean">Euclidean Distance</option>
<option value="manhattan">Manhattan Distance</option>
<option value="dotProduct">Dot Product Distance</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">HNSW Parameters</label>
<div class="row g-2">
<div class="col-6">
<div class="input-group">
<span class="input-group-text">M</span>
<input
type="number"
class="form-control"
id="paramM"
value="16"/>
</div>
</div>
<div class="col-6">
<div class="input-group">
<span class="input-group-text">efConstruction</span>
<input
type="number"
class="form-control"
id="paramEfConstruction"
value="200"/>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="d-flex gap-2">
<button id="initBtn" class="btn btn-primary">
<i class="bi bi-play-fill"></i> Initialize Database
</button>
<button id="clearBtn" class="btn btn-danger" disabled>
<i class="bi bi-trash"></i> Clear Database
</button>
<button id="sizeBtn" class="btn btn-secondary" disabled>
<i class="bi bi-info-circle"></i> Database Size
</button>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<span>Add Vectors</span>
</div>
<div class="card-body">
<button id="addSampleBtn" class="btn btn-success mb-3" disabled>
<i class="bi bi-plus-circle"></i> Add Sample Vectors
</button>
<div class="mb-3">
<label for="customVectorName" class="form-label"
>Custom Vector</label
>
<input
type="text"
class="form-control mb-2"
id="customVectorName"
placeholder="Name (e.g., elephant)"/>
<input
type="text"
class="form-control mb-2"
id="customVectorValues"
placeholder="Vector Values (comma-separated, e.g., 0.1, 0.2, 0.3, 0.4)"/>
<textarea
class="form-control"
id="customVectorMetadata"
rows="2"
placeholder='Metadata (JSON, e.g., {"type": "mammal", "size": "large"})'></textarea>
</div>
<button id="addCustomBtn" class="btn btn-success" disabled>
<i class="bi bi-plus-circle"></i> Add Custom Vector
</button>
<hr class="my-4">
<div class="mb-3">
<label for="textToAddInput" class="form-label">Add Text (Auto-Embedded)</label>
<input
type="text"
class="form-control mb-2"
id="textToAddInput"
placeholder="Enter text to add (e.g., 'A beautiful sunset')"
disabled/>
<textarea
class="form-control"
id="textToAddMetadata"
rows="2"
placeholder='Metadata (JSON, e.g., {"category": "nature", "sentiment": "positive"})'
disabled></textarea>
</div>
<button id="addTextBtn" class="btn btn-success" disabled>
<i class="bi bi-plus-circle"></i> Add Text with Auto-Embedding
</button>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<span>Search & Operations</span>
</div>
<div class="card-body">
<div class="mb-3">
<label for="searchVector" class="form-label"
>Search Vector</label
>
<select class="form-select mb-2" id="searchVector" disabled>
<option value="">Select a vector</option>
</select>
<div class="input-group mb-2">
<span class="input-group-text">Results (k)</span>
<input
type="number"
class="form-control"
id="searchK"
value="3"
min="1"
max="10"/>
</div>
<button id="searchBtn" class="btn btn-primary" disabled>
<i class="bi bi-search"></i> Search
</button>
</div>
<div class="mb-3">
<label for="operationVector" class="form-label"
>Vector Operations</label
>
<select class="form-select mb-2" id="operationVector" disabled>
<option value="">Select a vector</option>
</select>
<div class="d-flex gap-2">
<button id="getBtn" class="btn btn-info" disabled>
<i class="bi bi-info-circle"></i> Get
</button>
<button id="updateBtn" class="btn btn-warning" disabled>
<i class="bi bi-pencil"></i> Update
</button>
<button id="deleteBtn" class="btn btn-danger" disabled>
<i class="bi bi-trash"></i> Delete
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<span>Text Search</span>
</div>
<div class="card-body">
<div class="mb-3">
<label for="textSearchInput" class="form-label"
>Search Term</label
>
<input
type="text"
class="form-control mb-2"
id="textSearchInput"
placeholder="Enter text to search (e.g., 'large mammal')"
disabled/>
<div class="input-group mb-2">
<span class="input-group-text">Results (k)</span>
<input
type="number"
class="form-control"
id="textSearchK"
value="3"
min="1"
max="10"
disabled/>
</div>
<button id="textSearchBtn" class="btn btn-primary" disabled>
<i class="bi bi-search"></i> Text Search
</button>
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<span>Graph Visualization</span>
</div>
<div class="card-body">
<div class="mb-3">
<label class="form-label">Create Edge</label>
<div class="row g-2 mb-2">
<div class="col-5">
<select class="form-select" id="sourceNode" disabled>
<option value="">Source Node</option>
</select>
</div>
<div class="col-5">
<select class="form-select" id="targetNode" disabled>
<option value="">Target Node</option>
</select>
</div>
<div class="col-2">
<button
id="addEdgeBtn"
class="btn btn-success w-100"
disabled>
<i class="bi bi-link"></i>
</button>
</div>
</div>
<div class="mb-2">
<input
type="text"
class="form-control"
id="edgeType"
placeholder="Edge Type (e.g., 'is_related_to')"
disabled/>
</div>
</div>
<div
id="graphContainer"
class="border rounded p-2 mb-2"
style="height: 200px; background-color: #f8f9fa">
<div
class="text-center text-muted h-100 d-flex align-items-center justify-content-center">
Graph visualization will appear here
</div>
</div>
<button id="visualizeGraphBtn" class="btn btn-info" disabled>
<i class="bi bi-diagram-3"></i> Visualize Graph
</button>
</div>
</div>
</div>
</div>
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<span>Console Output</span>
</div>
<div class="card-body">
<pre id="output">// Output will appear here</pre>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script type="module">
// Import the BrainyData from your library
import {
BrainyData,
euclideanDistance,
cosineDistance,
manhattanDistance,
dotProductDistance
} from '../dist/index.js'
// Example data - word embeddings
const wordEmbeddings = {
cat: [0.2, 0.3, 0.4, 0.1],
dog: [0.3, 0.2, 0.4, 0.2],
fish: [0.1, 0.1, 0.8, 0.2],
bird: [0.1, 0.4, 0.2, 0.5],
tiger: [0.3, 0.4, 0.3, 0.1],
lion: [0.4, 0.3, 0.2, 0.1],
shark: [0.2, 0.1, 0.7, 0.3],
eagle: [0.2, 0.5, 0.1, 0.4]
}
// Example metadata
const metadata = {
cat: {type: 'mammal', domesticated: true},
dog: {type: 'mammal', domesticated: true},
fish: {type: 'fish', domesticated: false},
bird: {type: 'bird', domesticated: false},
tiger: {type: 'mammal', domesticated: false},
lion: {type: 'mammal', domesticated: false},
shark: {type: 'fish', domesticated: false},
eagle: {type: 'bird', domesticated: false}
}
// Get DOM elements
const outputEl = document.getElementById('output')
const initBtn = document.getElementById('initBtn')
const clearBtn = document.getElementById('clearBtn')
const sizeBtn = document.getElementById('sizeBtn')
const addSampleBtn = document.getElementById('addSampleBtn')
const addCustomBtn = document.getElementById('addCustomBtn')
const searchBtn = document.getElementById('searchBtn')
const getBtn = document.getElementById('getBtn')
const updateBtn = document.getElementById('updateBtn')
const deleteBtn = document.getElementById('deleteBtn')
const textSearchBtn = document.getElementById('textSearchBtn')
const addEdgeBtn = document.getElementById('addEdgeBtn')
const visualizeGraphBtn = document.getElementById('visualizeGraphBtn')
// Form elements
const distanceFunctionSelect = document.getElementById('distanceFunction')
const paramM = document.getElementById('paramM')
const paramEfConstruction = document.getElementById('paramEfConstruction')
const searchVector = document.getElementById('searchVector')
const searchK = document.getElementById('searchK')
const operationVector = document.getElementById('operationVector')
const customVectorName = document.getElementById('customVectorName')
const customVectorValues = document.getElementById('customVectorValues')
const customVectorMetadata = document.getElementById(
'customVectorMetadata'
)
const textSearchInput = document.getElementById('textSearchInput')
const textSearchK = document.getElementById('textSearchK')
const textToAddInput = document.getElementById('textToAddInput')
const textToAddMetadata = document.getElementById('textToAddMetadata')
const sourceNode = document.getElementById('sourceNode')
const targetNode = document.getElementById('targetNode')
const edgeType = document.getElementById('edgeType')
const graphContainer = document.getElementById('graphContainer')
// Create a new vector database
let db
const ids = {}
// Reference to the add text button
const addTextBtn = document.getElementById('addTextBtn')
// Helper function to log output
function log(message) {
if (typeof message === 'object') {
outputEl.textContent = JSON.stringify(message, null, 2)
} else {
outputEl.textContent = message
}
}
// Helper function to update vector selects
function updateVectorSelects() {
// Clear existing options
searchVector.innerHTML = '<option value="">Select a vector</option>'
operationVector.innerHTML = '<option value="">Select a vector</option>'
sourceNode.innerHTML = '<option value="">Source Node</option>'
targetNode.innerHTML = '<option value="">Target Node</option>'
// Add options for each vector
for (const [word, id] of Object.entries(ids)) {
const searchOption = document.createElement('option')
searchOption.value = word
searchOption.textContent = word
searchVector.appendChild(searchOption)
const operationOption = document.createElement('option')
operationOption.value = id
operationOption.textContent = word
operationVector.appendChild(operationOption)
// Add options for source and target node selects
const sourceOption = document.createElement('option')
sourceOption.value = id
sourceOption.textContent = word
sourceNode.appendChild(sourceOption)
const targetOption = document.createElement('option')
targetOption.value = id
targetOption.textContent = word
targetNode.appendChild(targetOption)
}
}
// Get the selected distance function
function getSelectedDistanceFunction() {
const selected = distanceFunctionSelect.value
switch (selected) {
case 'euclidean':
return euclideanDistance
case 'manhattan':
return manhattanDistance
case 'dotProduct':
return dotProductDistance
default:
return cosineDistance
}
}
// Simple function to convert text to vector
function textToVector(text) {
// This is a very simple implementation for demo purposes
// In a real application, you would use a proper embedding model
// Normalize the text
const normalizedText = text.toLowerCase().trim()
// Create a simple 4-dimensional vector based on character frequencies
const vector = [0, 0, 0, 0]
// Count vowels, consonants, numbers, and special characters
for (let i = 0; i < normalizedText.length; i++) {
const char = normalizedText[i]
if ('aeiou'.includes(char)) {
vector[0] += 0.1 // Vowels affect first dimension
} else if ('bcdfghjklmnpqrstvwxyz'.includes(char)) {
vector[1] += 0.1 // Consonants affect second dimension
} else if ('0123456789'.includes(char)) {
vector[2] += 0.1 // Numbers affect third dimension
} else {
vector[3] += 0.1 // Special chars affect fourth dimension
}
}
// Normalize the vector
const magnitude = Math.sqrt(
vector.reduce((sum, val) => sum + val * val, 0)
)
if (magnitude > 0) {
return vector.map((val) => val / magnitude)
}
return vector
}
// Function to render a simple graph visualization
function renderGraph(edges) {
if (!edges || edges.length === 0) {
graphContainer.innerHTML =
'<div class="text-center text-muted h-100 d-flex align-items-center justify-content-center">No edges to display</div>'
return
}
// Create a simple HTML representation of the graph
let html = '<div class="p-2">'
// Group edges by source
const edgesBySource = {}
for (const edge of edges) {
if (!edgesBySource[edge.sourceId]) {
edgesBySource[edge.sourceId] = []
}
edgesBySource[edge.sourceId].push(edge)
}
// Create a simple representation of nodes and edges
for (const [sourceId, sourceEdges] of Object.entries(edgesBySource)) {
const sourceName =
Object.entries(ids).find(([_, id]) => id === sourceId)?.[0] ||
'unknown'
html += `<div class="mb-2"><strong>${sourceName}</strong></div>`
for (const edge of sourceEdges) {
const targetName =
Object.entries(ids).find(
([_, id]) => id === edge.targetId
)?.[0] || 'unknown'
const edgeLabel = edge.type || 'connected_to'
html += `<div class="ms-4 mb-1">→ <span class="text-primary">${edgeLabel}</span> → ${targetName}</div>`
}
}
html += '</div>'
graphContainer.innerHTML = html
}
// Initialize the database
initBtn.addEventListener('click', async () => {
try {
log('Initializing database...')
// Get configuration values
const distanceFunction = getSelectedDistanceFunction()
const M = parseInt(paramM.value)
const efConstruction = parseInt(paramEfConstruction.value)
// Create the database with configuration
db = new BrainyData({
hnsw: {
M,
efConstruction
},
distanceFunction
})
await db.init()
log('Database initialized successfully')
// Enable buttons
initBtn.disabled = true
clearBtn.disabled = false
sizeBtn.disabled = false
addSampleBtn.disabled = false
addCustomBtn.disabled = false
textSearchInput.disabled = false
textSearchK.disabled = false
textSearchBtn.disabled = false
textToAddInput.disabled = false
textToAddMetadata.disabled = false
addTextBtn.disabled = false
edgeType.disabled = false
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Add sample vectors
addSampleBtn.addEventListener('click', async () => {
try {
log('Adding sample vectors...')
// Add vectors to the database
for (const [word, vector] of Object.entries(wordEmbeddings)) {
ids[word] = await db.add(vector, metadata[word])
}
log(
`Added ${Object.keys(ids).length} vectors:\n${JSON.stringify(ids, null, 2)}`
)
// Update vector selects
updateVectorSelects()
// Enable search and operation buttons
searchBtn.disabled = false
getBtn.disabled = false
updateBtn.disabled = false
deleteBtn.disabled = false
sourceNode.disabled = false
targetNode.disabled = false
addEdgeBtn.disabled = false
visualizeGraphBtn.disabled = false
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Add custom vector
addCustomBtn.addEventListener('click', async () => {
try {
const name = customVectorName.value.trim()
const vectorStr = customVectorValues.value.trim()
const metadataStr = customVectorMetadata.value.trim()
if (!name || !vectorStr) {
log('Please provide a name and vector values')
return
}
// Parse vector values
const vector = vectorStr.split(',').map((v) => parseFloat(v.trim()))
// Parse metadata
let metadataObj = {}
if (metadataStr) {
try {
metadataObj = JSON.parse(metadataStr)
} catch (e) {
log('Invalid JSON in metadata. Using empty object.')
}
}
log(`Adding custom vector "${name}"...`)
// Add to database
ids[name] = await db.add(vector, metadataObj)
log(`Added custom vector "${name}" with ID: ${ids[name]}`)
// Update vector selects
updateVectorSelects()
// Enable search and operation buttons
searchBtn.disabled = false
getBtn.disabled = false
updateBtn.disabled = false
deleteBtn.disabled = false
// Clear inputs
customVectorName.value = ''
customVectorValues.value = ''
customVectorMetadata.value = ''
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Search for similar vectors
searchBtn.addEventListener('click', async () => {
try {
const selected = searchVector.value
const k = parseInt(searchK.value)
if (!selected) {
log('Please select a vector to search')
return
}
log(`Searching for vectors similar to "${selected}"...`)
// Get the vector to search for - either from wordEmbeddings or by retrieving it from the database
let searchVector
if (wordEmbeddings[selected]) {
searchVector = wordEmbeddings[selected]
} else {
// For custom vectors, retrieve the vector from the database using the ID
const id = ids[selected]
if (id) {
const doc = await db.get(id)
if (doc && doc.vector) {
searchVector = doc.vector
} else {
throw new Error('Failed to retrieve vector from database')
}
} else {
throw new Error('Vector ID not found')
}
}
const results = await db.search(searchVector, k)
// Format results for display
const formattedResults = results.map((result) => {
const word =
Object.entries(ids).find(([_, id]) => id === result.id)?.[0] ||
'unknown'
return {
word,
score: result.score,
metadata: result.metadata
}
})
log(
`Search results for "${selected}":\n${JSON.stringify(formattedResults, null, 2)}`
)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Get a vector by ID
getBtn.addEventListener('click', async () => {
try {
const id = operationVector.value
if (!id) {
log('Please select a vector')
return
}
log(`Getting vector with ID ${id}...`)
const result = await db.get(id)
log(`Vector details:\n${JSON.stringify(result, null, 2)}`)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Update metadata
updateBtn.addEventListener('click', async () => {
try {
const id = operationVector.value
if (!id) {
log('Please select a vector')
return
}
// Get current metadata
const doc = await db.get(id)
const word =
Object.entries(ids).find(([_, docId]) => docId === id)?.[0] ||
'unknown'
// Prompt for new metadata
const newMetadataStr = prompt(
'Enter new metadata (JSON):',
JSON.stringify(doc.metadata)
)
if (newMetadataStr === null) {
return // User cancelled
}
// Parse new metadata
let newMetadata
try {
newMetadata = JSON.parse(newMetadataStr)
} catch (e) {
log('Invalid JSON. Update cancelled.')
return
}
log(`Updating metadata for "${word}"...`)
await db.updateMetadata(id, newMetadata)
// Get updated document
const updatedDoc = await db.get(id)
log(`Updated metadata:\n${JSON.stringify(updatedDoc, null, 2)}`)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Delete a vector
deleteBtn.addEventListener('click', async () => {
try {
const id = operationVector.value
if (!id) {
log('Please select a vector')
return
}
const word =
Object.entries(ids).find(([_, docId]) => docId === id)?.[0] ||
'unknown'
if (!confirm(`Are you sure you want to delete "${word}"?`)) {
return
}
log(`Deleting vector "${word}"...`)
await db.delete(id)
// Remove from ids
delete ids[word]
log(`Deleted vector "${word}". Database size: ${db.size()}`)
// Update vector selects
updateVectorSelects()
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Clear the database
clearBtn.addEventListener('click', async () => {
try {
if (!confirm('Are you sure you want to clear the database?')) {
return
}
log('Clearing database...')
await db.clear()
log('Database cleared successfully')
// Clear ids
Object.keys(ids).forEach((key) => delete ids[key])
// Update vector selects
updateVectorSelects()
// Disable buttons
searchBtn.disabled = true
getBtn.disabled = true
updateBtn.disabled = true
deleteBtn.disabled = true
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Get database size
sizeBtn.addEventListener('click', () => {
try {
const size = db.size()
log(`Database size: ${size} vectors`)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Text search
textSearchBtn.addEventListener('click', async () => {
try {
const searchTerm = textSearchInput.value.trim()
const k = parseInt(textSearchK.value)
if (!searchTerm) {
log('Please enter a search term')
return
}
log(`Searching for "${searchTerm}" using automatic embedding...`)
// Search directly with the text - it will be automatically embedded
const results = await db.search(searchTerm, k)
// Format results for display
const formattedResults = results.map((result) => {
const word =
Object.entries(ids).find(([_, id]) => id === result.id)?.[0] ||
'unknown'
return {
word,
score: result.score,
metadata: result.metadata
}
})
log(
`Text search results for "${searchTerm}":\n${JSON.stringify(formattedResults, null, 2)}`
)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Add edge between nodes
addEdgeBtn.addEventListener('click', async () => {
try {
const sourceId = sourceNode.value
const targetId = targetNode.value
const type = edgeType.value.trim() || 'connected_to'
if (!sourceId || !targetId) {
log('Please select both source and target nodes')
return
}
if (sourceId === targetId) {
log('Source and target nodes must be different')
return
}
log(
`Creating edge from ${sourceId} to ${targetId} with type "${type}"...`
)
// Add the edge
const edgeId = await db.addEdge(sourceId, targetId, null, {type})
log(`Edge created with ID: ${edgeId}`)
// Visualize the graph
const edges = await db.getAllEdges()
renderGraph(edges)
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Add text with auto-embedding
addTextBtn.addEventListener('click', async () => {
try {
const text = textToAddInput.value.trim()
const metadataStr = textToAddMetadata.value.trim()
if (!text) {
log('Please enter text to add')
return
}
// Parse metadata
let metadataObj = {}
if (metadataStr) {
try {
metadataObj = JSON.parse(metadataStr)
} catch (e) {
log('Invalid JSON in metadata. Using empty object.')
}
}
log(`Adding text "${text}" with auto-embedding...`)
// Add to database - the text will be automatically embedded
const id = await db.add(text, metadataObj)
// Use the first few words as the name
const name = text.split(' ').slice(0, 3).join(' ') + '...'
ids[name] = id
log(`Added text with ID: ${id}`)
// Update vector selects
updateVectorSelects()
// Enable search and operation buttons
searchBtn.disabled = false
getBtn.disabled = false
updateBtn.disabled = false
deleteBtn.disabled = false
sourceNode.disabled = false
targetNode.disabled = false
addEdgeBtn.disabled = false
visualizeGraphBtn.disabled = false
// Clear inputs
textToAddInput.value = ''
textToAddMetadata.value = ''
} catch (error) {
log(`Error: ${error.message}`)
}
})
// Visualize graph
visualizeGraphBtn.addEventListener('click', async () => {
try {
log('Fetching all edges and visualizing graph...')
const edges = await db.getAllEdges()
if (edges.length === 0) {
log('No edges found. Create some edges first.')
return
}
log(`Found ${edges.length} edges. Rendering graph...`)
renderGraph(edges)
} catch (error) {
log(`Error: ${error.message}`)
}
})
</script>
</body>
</html>