feat: add browser-server search example with server search augmentations

This commit is contained in:
David Snelling 2025-06-06 10:48:10 -07:00
parent bf3d9a055b
commit a206b9926d
7 changed files with 2021 additions and 0 deletions

63
examples/README.md Normal file
View file

@ -0,0 +1,63 @@
# Brainy Examples
This directory contains examples demonstrating various features and use cases of the Brainy vector graph database.
## Browser-Server Search Example
The [browser-server-search](./browser-server-search/) example demonstrates how to use Brainy in a browser, call a server-hosted version for search, store the results locally, and then perform further searches against the local instance.
This approach allows you to:
- Search a server-hosted Brainy instance from a browser
- Store the search results in a local Brainy instance
- Perform further searches against the local instance without needing to query the server again
- Add data to both local and server instances
See the [browser-server-search README](./browser-server-search/README.md) for detailed instructions.
## Other Examples
### Augmentation Examples
- [conduitAugmentationExample.js](./conduitAugmentationExample.js) - Demonstrates how to use conduit augmentations for syncing Brainy instances
- [memoryAugmentationExample.js](./memoryAugmentationExample.js) - Shows how to use memory augmentations for custom storage
### Pipeline Examples
- [sequentialPipelineExample.js](./sequentialPipelineExample.js) - Demonstrates the sequential pipeline for processing data
### Demo
- [demo.html](./demo.html) - A web demo showcasing Brainy's capabilities
### Configuration Examples
- [configurationTest.js](./configurationTest.js) - Shows how to configure Brainy with custom options
- [readOnlyTest.js](./readOnlyTest.js) - Demonstrates using Brainy in read-only mode
- [buildTimeRegistration.js](./buildTimeRegistration.js) - Shows how to register augmentations at build time
### Data Inspection
- [dataInspectionExample.js](./dataInspectionExample.js) - Demonstrates how to inspect data stored in Brainy
## Running the Examples
Most JavaScript examples can be run using Node.js:
```bash
node examples/sequentialPipelineExample.js
```
For HTML examples, you can open them directly in a browser or serve them using a local HTTP server:
```bash
# Using a simple HTTP server
npx http-server
```
Then navigate to the appropriate URL in your browser (e.g., http://localhost:8080/examples/demo.html).
## Creating Your Own Examples
Feel free to use these examples as a starting point for your own projects. You can copy and modify them to suit your needs.
If you create an example that might be useful to others, consider contributing it back to the Brainy project!

View file

@ -0,0 +1,187 @@
# Brainy Browser-Server Search Example
This example demonstrates how to use Brainy in a browser, call a server-hosted version for search, store the results locally, and then perform further searches against the local instance.
## Overview
The solution consists of:
1. A `BrainyServerSearch` class that handles the connection to the server and local storage
2. An HTML interface for testing the functionality
3. Server-side setup using the Brainy cloud wrapper
This approach allows you to:
- Search a server-hosted Brainy instance from a browser
- Store the search results in a local Brainy instance
- Perform further searches against the local instance without needing to query the server again
- Add data to both local and server instances
## How It Works
1. The browser creates a local Brainy instance
2. It connects to the server-hosted Brainy instance using WebSocket
3. When a search is performed:
- The query is sent to the server
- The server returns the search results
- The results are stored in the local Brainy instance
- The results are displayed to the user
4. Subsequent searches can be performed against the local instance
5. A combined search mode first checks the local instance and then queries the server only if needed
## Setup Instructions
### Server Setup
1. Set up the Brainy cloud wrapper:
```bash
# Clone the repository if you haven't already
git clone https://github.com/soulcraft/brainy.git
cd brainy/cloud-wrapper
# Install dependencies
npm install --legacy-peer-deps
# Configure the server
cp .env.example .env
# Edit .env to configure your environment
# Build and start the server
npm run build
npm run start
```
2. Note the WebSocket URL of your server (e.g., `wss://your-server.com/ws` or `ws://localhost:3000/ws` for local development)
### Client Setup
1. Copy the example files to your project:
```bash
cp -r examples/browser-server-search your-project/
```
2. Include the Brainy library in your project:
```bash
npm install @soulcraft/brainy --legacy-peer-deps
```
3. Open the HTML file in a browser or serve it using a local server:
```bash
# Using a simple HTTP server
cd your-project
npx http-server
```
4. Navigate to http://localhost:8080/browser-server-search/ in your browser
5. Enter the WebSocket URL of your server and start using the example
## Usage
### Using the HTML Interface
1. Enter the WebSocket URL of your Brainy server
2. Click "Connect" to establish a connection
3. Enter a search query and click one of the search buttons:
- "Search Server" - Search the server and store results locally
- "Search Local" - Search only the local instance
- "Search Combined" - Search local first, then server if needed
4. To add data, enter text in the "Add Data" field and click "Add to Both"
### Using the BrainyServerSearch Class in Your Code
```javascript
import { BrainyServerSearch } from './index.js';
// Create a new instance
const brainySearch = new BrainyServerSearch('wss://your-brainy-server.com/ws');
// Initialize and connect
await brainySearch.init();
// Search the server and store results locally
const serverResults = await brainySearch.searchServer('machine learning', 5);
// Search the local instance
const localResults = await brainySearch.searchLocal('machine learning', 5);
// Perform a combined search
const combinedResults = await brainySearch.searchCombined('neural networks', 5);
// Add data to both local and server
const id = await brainySearch.add('Deep learning is a subset of machine learning', {
noun: 'Concept',
category: 'AI',
tags: ['deep learning', 'neural networks']
});
// Close the connection when done
await brainySearch.close();
```
## API Reference
### BrainyServerSearch Class
#### Constructor
```javascript
const brainySearch = new BrainyServerSearch(serverUrl);
```
- `serverUrl` (string): WebSocket URL of the Brainy server
#### Methods
- `init()`: Initialize the local Brainy instance and connect to the server
- `searchServer(query, limit = 10)`: Search the server-hosted Brainy instance, store results locally, and return them
- `searchLocal(query, limit = 10)`: Search the local Brainy instance
- `searchCombined(query, limit = 10)`: Search both server and local instances, combine results, and store server results locally
- `add(data, metadata = {})`: Add data to both local and server instances
- `close()`: Close the connection to the server
## Advanced Configuration
### Custom Embedding Function
You can customize the embedding function used by the local Brainy instance:
```javascript
import { createSimpleEmbeddingFunction } from '@soulcraft/brainy';
// In your code, before calling init():
brainySearch.setEmbeddingFunction(createSimpleEmbeddingFunction());
```
### Persistent Storage
To enable persistent storage for the local Brainy instance:
```javascript
// In your code, before calling init():
brainySearch.setStorageOptions({
requestPersistentStorage: true
});
```
## Troubleshooting
### Connection Issues
- Ensure the server is running and accessible
- Check that the WebSocket URL is correct
- Verify that your browser supports WebSockets
- Check for CORS issues if the server is on a different domain
### Search Issues
- Ensure the server has data to search
- Check that the query is not empty
- Verify that the server is properly configured for search
## License
MIT

View file

@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brainy Browser-Server Search Example</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
line-height: 1.6;
}
h1, h2 {
color: #333;
}
pre {
background-color: #f5f5f5;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
}
button {
background-color: #4CAF50;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
border-radius: 5px;
}
input[type="text"] {
padding: 10px;
width: 70%;
margin-right: 10px;
border-radius: 5px;
border: 1px solid #ddd;
}
.result-container {
margin-top: 20px;
border: 1px solid #ddd;
padding: 10px;
border-radius: 5px;
max-height: 400px;
overflow-y: auto;
}
.log {
margin-top: 10px;
color: #666;
}
.error {
color: red;
}
</style>
</head>
<body>
<h1>Brainy Browser-Server Search Example</h1>
<p>
This example demonstrates how to use Brainy in a browser, call a server-hosted version for search,
store the results locally, and then perform further searches against the local instance.
</p>
<div>
<h2>Server URL</h2>
<input type="text" id="serverUrl" value="wss://your-brainy-server.com/ws" placeholder="WebSocket URL of your Brainy server">
<button id="connectBtn">Connect</button>
</div>
<div>
<h2>Search</h2>
<input type="text" id="searchQuery" placeholder="Enter search query">
<button id="searchServerBtn">Search Server</button>
<button id="searchLocalBtn">Search Local</button>
<button id="searchCombinedBtn">Search Combined</button>
</div>
<div>
<h2>Add Data</h2>
<input type="text" id="addData" placeholder="Enter text to add">
<button id="addBtn">Add to Both</button>
</div>
<div class="result-container">
<h2>Results</h2>
<pre id="results">Connect to a server to begin...</pre>
</div>
<div class="log" id="log"></div>
<!-- Import the Brainy library -->
<script type="module">
// Import the BrainyServerSearch class
import { BrainyServerSearch } from './index.js';
// Global variables
let brainySearch = null;
// DOM elements
const serverUrlInput = document.getElementById('serverUrl');
const connectBtn = document.getElementById('connectBtn');
const searchQueryInput = document.getElementById('searchQuery');
const searchServerBtn = document.getElementById('searchServerBtn');
const searchLocalBtn = document.getElementById('searchLocalBtn');
const searchCombinedBtn = document.getElementById('searchCombinedBtn');
const addDataInput = document.getElementById('addData');
const addBtn = document.getElementById('addBtn');
const resultsElement = document.getElementById('results');
const logElement = document.getElementById('log');
// Helper functions
function log(message, isError = false) {
const div = document.createElement('div');
div.textContent = message;
if (isError) {
div.classList.add('error');
}
logElement.appendChild(div);
logElement.scrollTop = logElement.scrollHeight;
}
function displayResults(results) {
resultsElement.textContent = JSON.stringify(results, null, 2);
}
// Event handlers
connectBtn.addEventListener('click', async () => {
const serverUrl = serverUrlInput.value.trim();
if (!serverUrl) {
log('Please enter a server URL', true);
return;
}
try {
log(`Connecting to ${serverUrl}...`);
brainySearch = new BrainyServerSearch(serverUrl);
await brainySearch.init();
log('Connected successfully!');
displayResults({ status: 'Connected', serverUrl });
} catch (error) {
log(`Connection error: ${error.message}`, true);
displayResults({ error: error.message });
}
});
searchServerBtn.addEventListener('click', async () => {
if (!brainySearch) {
log('Please connect to a server first', true);
return;
}
const query = searchQueryInput.value.trim();
if (!query) {
log('Please enter a search query', true);
return;
}
try {
log(`Searching server for "${query}"...`);
const results = await brainySearch.searchServer(query, 5);
log(`Found ${results.length} results from server`);
displayResults(results);
} catch (error) {
log(`Search error: ${error.message}`, true);
displayResults({ error: error.message });
}
});
searchLocalBtn.addEventListener('click', async () => {
if (!brainySearch) {
log('Please connect to a server first', true);
return;
}
const query = searchQueryInput.value.trim();
if (!query) {
log('Please enter a search query', true);
return;
}
try {
log(`Searching local database for "${query}"...`);
const results = await brainySearch.searchLocal(query, 5);
log(`Found ${results.length} results locally`);
displayResults(results);
} catch (error) {
log(`Search error: ${error.message}`, true);
displayResults({ error: error.message });
}
});
searchCombinedBtn.addEventListener('click', async () => {
if (!brainySearch) {
log('Please connect to a server first', true);
return;
}
const query = searchQueryInput.value.trim();
if (!query) {
log('Please enter a search query', true);
return;
}
try {
log(`Performing combined search for "${query}"...`);
const results = await brainySearch.searchCombined(query, 5);
log(`Found ${results.length} combined results`);
displayResults(results);
} catch (error) {
log(`Search error: ${error.message}`, true);
displayResults({ error: error.message });
}
});
addBtn.addEventListener('click', async () => {
if (!brainySearch) {
log('Please connect to a server first', true);
return;
}
const data = addDataInput.value.trim();
if (!data) {
log('Please enter data to add', true);
return;
}
try {
log(`Adding data: "${data}"...`);
const id = await brainySearch.add(data, {
noun: 'Concept',
category: 'UserInput',
timestamp: new Date().toISOString()
});
log(`Added data with ID: ${id}`);
displayResults({ success: true, id, data });
} catch (error) {
log(`Add error: ${error.message}`, true);
displayResults({ error: error.message });
}
});
// Initial log
log('Ready to connect to a Brainy server');
</script>
</body>
</html>

View file

@ -0,0 +1,278 @@
// Browser-Server Search Example
// This example demonstrates how to use Brainy in a browser, call a server-hosted version for search,
// store the results locally, and then perform further searches against the local instance.
import {
BrainyData,
augmentationPipeline,
createConduitAugmentation,
NounType
} from '@soulcraft/brainy';
/**
* BrainyServerSearch class
* Provides functionality to search a server-hosted Brainy instance and store results locally
*/
class BrainyServerSearch {
constructor(serverUrl) {
this.serverUrl = serverUrl;
this.localDb = null;
this.wsConduit = null;
this.connection = null;
this.isInitialized = false;
}
/**
* Initialize the local Brainy instance and connect to the server
*/
async init() {
if (this.isInitialized) {
return;
}
try {
// Initialize local Brainy instance
this.localDb = new BrainyData();
await this.localDb.init();
// Create a WebSocket conduit augmentation
this.wsConduit = await createConduitAugmentation('websocket', 'server-search-conduit');
// Register the augmentation with the pipeline
augmentationPipeline.register(this.wsConduit);
// Connect to the server
const connectionResult = await augmentationPipeline.executeConduitPipeline(
'establishConnection',
[this.serverUrl, { protocols: 'brainy-sync' }]
);
if (connectionResult[0] && (await connectionResult[0]).success) {
this.connection = (await connectionResult[0]).data;
console.log('Connected to server:', this.serverUrl);
this.isInitialized = true;
} else {
throw new Error('Failed to connect to server');
}
} catch (error) {
console.error('Failed to initialize BrainyServerSearch:', error);
throw error;
}
}
/**
* Search the server-hosted Brainy instance, store results locally, and return them
* @param {string} query - The search query
* @param {number} limit - Maximum number of results to return
* @returns {Promise<Array>} - Search results
*/
async searchServer(query, limit = 10) {
await this.ensureInitialized();
try {
// Create a search request
const readResult = await augmentationPipeline.executeConduitPipeline(
'readData',
[{
connectionId: this.connection.connectionId,
query: {
type: 'search',
query: query,
limit: limit
}
}]
);
if (readResult[0] && (await readResult[0]).success) {
const searchResults = (await readResult[0]).data;
// Store the results in the local Brainy instance
for (const result of searchResults) {
// Check if the noun already exists in the local database
const existingNoun = await this.localDb.get(result.id);
if (!existingNoun) {
// Add the noun to the local database
await this.localDb.add(result.vector, result.metadata);
}
}
return searchResults;
} else {
const error = readResult[0] ? (await readResult[0]).error : 'Unknown error';
throw new Error(`Failed to search server: ${error}`);
}
} catch (error) {
console.error('Error searching server:', error);
throw error;
}
}
/**
* Search the local Brainy instance
* @param {string} query - The search query
* @param {number} limit - Maximum number of results to return
* @returns {Promise<Array>} - Search results
*/
async searchLocal(query, limit = 10) {
await this.ensureInitialized();
try {
return await this.localDb.searchText(query, limit);
} catch (error) {
console.error('Error searching local database:', error);
throw error;
}
}
/**
* Search both server and local instances, combine results, and store server results locally
* @param {string} query - The search query
* @param {number} limit - Maximum number of results to return
* @returns {Promise<Array>} - Combined search results
*/
async searchCombined(query, limit = 10) {
await this.ensureInitialized();
try {
// Search local first
const localResults = await this.searchLocal(query, limit);
// If we have enough local results, return them
if (localResults.length >= limit) {
return localResults;
}
// Otherwise, search server for additional results
const serverResults = await this.searchServer(query, limit - localResults.length);
// Combine results, removing duplicates
const combinedResults = [...localResults];
const localIds = new Set(localResults.map(r => r.id));
for (const result of serverResults) {
if (!localIds.has(result.id)) {
combinedResults.push(result);
}
}
return combinedResults;
} catch (error) {
console.error('Error performing combined search:', error);
throw error;
}
}
/**
* Add data to both local and server instances
* @param {string|Array} data - Text or vector to add
* @param {Object} metadata - Metadata for the data
* @returns {Promise<string>} - ID of the added data
*/
async add(data, metadata = {}) {
await this.ensureInitialized();
try {
// Add to local first
const id = await this.localDb.add(data, metadata);
// Get the vector and metadata
const noun = await this.localDb.get(id);
// Add to server
await augmentationPipeline.executeConduitPipeline(
'writeData',
[{
connectionId: this.connection.connectionId,
data: {
type: 'addNoun',
vector: noun.vector,
metadata: noun.metadata
}
}]
);
return id;
} catch (error) {
console.error('Error adding data:', error);
throw error;
}
}
/**
* Ensure the instance is initialized
*/
async ensureInitialized() {
if (!this.isInitialized) {
await this.init();
}
}
/**
* Close the connection to the server
*/
async close() {
if (this.connection) {
try {
await this.wsConduit.closeWebSocket(this.connection.connectionId);
this.connection = null;
} catch (error) {
console.error('Error closing connection:', error);
}
}
this.isInitialized = false;
}
}
// Example usage
async function runExample() {
// Create a BrainyServerSearch instance
const brainySearch = new BrainyServerSearch('wss://your-brainy-server.com/ws');
try {
// Initialize
await brainySearch.init();
// Search the server and store results locally
console.log('Searching server for "machine learning"...');
const serverResults = await brainySearch.searchServer('machine learning', 5);
console.log('Server results:', serverResults);
// Now search locally - this should return the results we just stored
console.log('Searching local database for "machine learning"...');
const localResults = await brainySearch.searchLocal('machine learning', 5);
console.log('Local results:', localResults);
// Search for something related but different
console.log('Searching local database for "artificial intelligence"...');
const aiResults = await brainySearch.searchLocal('artificial intelligence', 5);
console.log('AI results:', aiResults);
// Perform a combined search
console.log('Performing combined search for "neural networks"...');
const combinedResults = await brainySearch.searchCombined('neural networks', 5);
console.log('Combined results:', combinedResults);
// Add new data to both local and server
console.log('Adding new data...');
const id = await brainySearch.add('Deep learning is a subset of machine learning', {
noun: NounType.Concept,
category: 'AI',
tags: ['deep learning', 'neural networks']
});
console.log('Added data with ID:', id);
// Close the connection
await brainySearch.close();
} catch (error) {
console.error('Example failed:', error);
}
}
// In a browser environment, you would call this when the page loads
// runExample();
// Export for use in other modules
export { BrainyServerSearch };

View file

@ -0,0 +1,346 @@
/**
* Server Search Augmentation Example
*
* This example demonstrates how to use the ServerSearchConduitAugmentation and
* ServerSearchActivationAugmentation to search a server-hosted Brainy instance,
* store results locally, and perform further searches against the local instance.
*/
import {
BrainyData,
augmentationPipeline,
AugmentationType,
NounType
} from '@soulcraft/brainy'
// Import the server search augmentations
import {
ServerSearchConduitAugmentation,
ServerSearchActivationAugmentation,
createServerSearchAugmentations
} from '../src/augmentations/serverSearchAugmentations.js'
/**
* Example 1: Using the factory function
*
* This is the simplest way to use the server search augmentations.
* The factory function creates both augmentations, links them together,
* and connects to the server.
*/
async function example1() {
console.log('Example 1: Using the factory function')
try {
// Create the augmentations and connect to the server
const { conduit, activation, connection } = await createServerSearchAugmentations(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
console.log('Connected to server with connection ID:', connection.connectionId)
// Search the server and store results locally
console.log('Searching server for "machine learning"...')
const serverSearchResult = await conduit.searchServer(
connection.connectionId,
'machine learning',
5
)
if (serverSearchResult.success) {
console.log('Server search results:', serverSearchResult.data)
} else {
console.error('Server search failed:', serverSearchResult.error)
}
// Now search locally - this should return the results we just stored
console.log('Searching local database for "machine learning"...')
const localSearchResult = await conduit.searchLocal('machine learning', 5)
if (localSearchResult.success) {
console.log('Local search results:', localSearchResult.data)
} else {
console.error('Local search failed:', localSearchResult.error)
}
// Perform a combined search
console.log('Performing combined search for "neural networks"...')
const combinedSearchResult = await conduit.searchCombined(
connection.connectionId,
'neural networks',
5
)
if (combinedSearchResult.success) {
console.log('Combined search results:', combinedSearchResult.data)
} else {
console.error('Combined search failed:', combinedSearchResult.error)
}
// Add data to both local and server
console.log('Adding data to both local and server...')
const addResult = await conduit.addToBoth(
connection.connectionId,
'Deep learning is a subset of machine learning',
{
noun: NounType.Concept,
category: 'AI',
tags: ['deep learning', 'neural networks']
}
)
if (addResult.success) {
console.log('Added data with ID:', addResult.data)
} else {
console.error('Failed to add data:', addResult.error)
}
} catch (error) {
console.error('Example 1 failed:', error)
}
}
/**
* Example 2: Using the activation augmentation
*
* This example demonstrates how to use the activation augmentation
* to trigger actions related to server search.
*/
async function example2() {
console.log('\nExample 2: Using the activation augmentation')
try {
// Create the augmentations and connect to the server
const { conduit, activation, connection } = await createServerSearchAugmentations(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
console.log('Connected to server with connection ID:', connection.connectionId)
// Use the activation augmentation to search the server
console.log('Using activation to search server for "machine learning"...')
const serverSearchAction = activation.triggerAction('searchServer', {
connectionId: connection.connectionId,
query: 'machine learning',
limit: 5
})
if (serverSearchAction.success) {
// The data property contains a promise that will resolve to the search results
const serverSearchResult = await serverSearchAction.data
console.log('Server search results:', serverSearchResult)
} else {
console.error('Server search action failed:', serverSearchAction.error)
}
// Use the activation augmentation to search locally
console.log('Using activation to search local database for "machine learning"...')
const localSearchAction = activation.triggerAction('searchLocal', {
query: 'machine learning',
limit: 5
})
if (localSearchAction.success) {
const localSearchResult = await localSearchAction.data
console.log('Local search results:', localSearchResult)
} else {
console.error('Local search action failed:', localSearchAction.error)
}
// Use the activation augmentation to perform a combined search
console.log('Using activation to perform combined search for "neural networks"...')
const combinedSearchAction = activation.triggerAction('searchCombined', {
connectionId: connection.connectionId,
query: 'neural networks',
limit: 5
})
if (combinedSearchAction.success) {
const combinedSearchResult = await combinedSearchAction.data
console.log('Combined search results:', combinedSearchResult)
} else {
console.error('Combined search action failed:', combinedSearchAction.error)
}
// Use the activation augmentation to add data to both local and server
console.log('Using activation to add data to both local and server...')
const addAction = activation.triggerAction('addToBoth', {
connectionId: connection.connectionId,
data: 'Deep learning is a subset of machine learning',
metadata: {
noun: NounType.Concept,
category: 'AI',
tags: ['deep learning', 'neural networks']
}
})
if (addAction.success) {
const addResult = await addAction.data
console.log('Added data with ID:', addResult)
} else {
console.error('Add action failed:', addAction.error)
}
} catch (error) {
console.error('Example 2 failed:', error)
}
}
/**
* Example 3: Using the augmentation pipeline
*
* This example demonstrates how to use the augmentation pipeline
* to execute the conduit and activation augmentations.
*/
async function example3() {
console.log('\nExample 3: Using the augmentation pipeline')
try {
// Create the augmentations and connect to the server
const { conduit, activation, connection } = await createServerSearchAugmentations(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
console.log('Connected to server with connection ID:', connection.connectionId)
// Use the augmentation pipeline to search the server
console.log('Using pipeline to search server...')
const conduitResults = await augmentationPipeline.executeConduitPipeline(
'searchServer',
[connection.connectionId, 'machine learning', 5]
)
if (conduitResults.length > 0 && (await conduitResults[0]).success) {
console.log('Server search results:', (await conduitResults[0]).data)
} else {
console.error('Server search failed')
}
// Use the augmentation pipeline to trigger the search action
console.log('Using pipeline to trigger search action...')
const activationResults = await augmentationPipeline.executeActivationPipeline(
'triggerAction',
['searchLocal', { query: 'machine learning', limit: 5 }]
)
if (activationResults.length > 0 && (await activationResults[0]).success) {
const actionResult = (await activationResults[0]).data
if (actionResult.success) {
const searchResult = await actionResult.data
console.log('Local search results:', searchResult)
}
} else {
console.error('Search action failed')
}
} catch (error) {
console.error('Example 3 failed:', error)
}
}
/**
* Example 4: Creating and using the augmentations manually
*
* This example demonstrates how to create and use the augmentations
* without using the factory function.
*/
async function example4() {
console.log('\nExample 4: Creating and using the augmentations manually')
try {
// Create a local Brainy instance
const localDb = new BrainyData()
await localDb.init()
// Create the conduit augmentation
const conduit = new ServerSearchConduitAugmentation('manual-server-search-conduit')
conduit.setLocalDb(localDb)
await conduit.initialize()
// Create the activation augmentation
const activation = new ServerSearchActivationAugmentation('manual-server-search-activation')
activation.setConduitAugmentation(conduit)
await activation.initialize()
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
// Connect to the server
console.log('Connecting to server...')
const connectionResult = await conduit.establishConnection(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
if (!connectionResult.success || !connectionResult.data) {
throw new Error(`Failed to connect to server: ${connectionResult.error}`)
}
const connection = connectionResult.data
console.log('Connected to server with connection ID:', connection.connectionId)
// Store the connection in the activation augmentation
activation.storeConnection(connection.connectionId, connection)
// Search the server
console.log('Searching server for "machine learning"...')
const serverSearchResult = await conduit.searchServer(
connection.connectionId,
'machine learning',
5
)
if (serverSearchResult.success) {
console.log('Server search results:', serverSearchResult.data)
} else {
console.error('Server search failed:', serverSearchResult.error)
}
} catch (error) {
console.error('Example 4 failed:', error)
}
}
/**
* Run all examples
*/
async function runExamples() {
// Initialize the augmentation pipeline
await augmentationPipeline.initialize()
// Run the examples
await example1()
await example2()
await example3()
await example4()
// Shut down the augmentation pipeline
await augmentationPipeline.shutDown()
}
// Run the examples
// runExamples().catch(console.error)
// Export for use in other modules
export {
example1,
example2,
example3,
example4,
runExamples
}

230
src/augmentations/README.md Normal file
View file

@ -0,0 +1,230 @@
# Brainy Augmentations
This directory contains the augmentation implementations for Brainy. Augmentations are pluggable components that extend Brainy's functionality in various ways.
## Available Augmentations
### Conduit Augmentations
Conduit augmentations provide data synchronization between Brainy instances.
#### WebSocketConduitAugmentation
A conduit augmentation that syncs Brainy instances using WebSockets. This is used for syncing between browsers and servers, or between servers.
```javascript
import { createConduitAugmentation, augmentationPipeline } from '@soulcraft/brainy'
// Create a WebSocket conduit augmentation
const wsConduit = await createConduitAugmentation('websocket', 'my-websocket-sync')
// Register the augmentation with the pipeline
augmentationPipeline.register(wsConduit)
// Connect to another Brainy instance
const connectionResult = await wsConduit.establishConnection(
'wss://your-websocket-server.com/brainy-sync',
{ protocols: 'brainy-sync' }
)
```
#### WebRTCConduitAugmentation
A conduit augmentation that syncs Brainy instances using WebRTC. This is used for direct peer-to-peer syncing between browsers.
```javascript
import { createConduitAugmentation, augmentationPipeline } from '@soulcraft/brainy'
// Create a WebRTC conduit augmentation
const webrtcConduit = await createConduitAugmentation('webrtc', 'my-webrtc-sync')
// Register the augmentation with the pipeline
augmentationPipeline.register(webrtcConduit)
// Connect to a peer
const connectionResult = await webrtcConduit.establishConnection(
'peer-id-to-connect-to',
{
signalServerUrl: 'wss://your-signal-server.com',
localPeerId: 'my-peer-id',
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
}
)
```
#### ServerSearchConduitAugmentation
A specialized conduit augmentation that provides functionality for searching a server-hosted Brainy instance and storing results locally. This allows you to:
- Search a server-hosted Brainy instance from a browser
- Store the search results in a local Brainy instance
- Perform further searches against the local instance without needing to query the server again
- Add data to both local and server instances
```javascript
import {
ServerSearchConduitAugmentation,
createServerSearchAugmentations,
augmentationPipeline
} from '@soulcraft/brainy'
// Using the factory function (recommended)
const { conduit, activation, connection } = await createServerSearchAugmentations(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
// Search the server and store results locally
const serverSearchResult = await conduit.searchServer(
connection.connectionId,
'your search query',
5 // limit
)
// Search the local instance
const localSearchResult = await conduit.searchLocal('your search query', 5)
// Perform a combined search (local first, then server if needed)
const combinedSearchResult = await conduit.searchCombined(
connection.connectionId,
'your search query',
5
)
// Add data to both local and server
const addResult = await conduit.addToBoth(
connection.connectionId,
'Text to add',
{ /* metadata */ }
)
```
### Activation Augmentations
Activation augmentations dictate how Brainy initiates actions, responses, or data manipulations.
#### ServerSearchActivationAugmentation
An activation augmentation that provides actions for server search functionality. This works in conjunction with the ServerSearchConduitAugmentation to provide a complete solution for browser-server search.
```javascript
import {
ServerSearchActivationAugmentation,
createServerSearchAugmentations,
augmentationPipeline
} from '@soulcraft/brainy'
// Using the factory function (recommended)
const { conduit, activation, connection } = await createServerSearchAugmentations(
'wss://your-brainy-server.com/ws',
{ protocols: 'brainy-sync' }
)
// Register the augmentations with the pipeline
augmentationPipeline.register(conduit)
augmentationPipeline.register(activation)
// Use the activation augmentation to search the server
const serverSearchAction = activation.triggerAction('searchServer', {
connectionId: connection.connectionId,
query: 'your search query',
limit: 5
})
if (serverSearchAction.success) {
// The data property contains a promise that will resolve to the search results
const serverSearchResult = await serverSearchAction.data
console.log('Server search results:', serverSearchResult)
}
// Other available actions:
// - 'connectToServer': Connect to a server
// - 'searchLocal': Search the local instance
// - 'searchCombined': Search both local and server
// - 'addToBoth': Add data to both local and server
```
## Using the Augmentation Pipeline
The augmentation pipeline provides a way to execute augmentations based on their type.
```javascript
import { augmentationPipeline } from '@soulcraft/brainy'
// Execute a conduit augmentation
const conduitResults = await augmentationPipeline.executeConduitPipeline(
'methodName',
[arg1, arg2, ...],
{ /* options */ }
)
// Execute an activation augmentation
const activationResults = await augmentationPipeline.executeActivationPipeline(
'methodName',
[arg1, arg2, ...],
{ /* options */ }
)
```
## Creating Custom Augmentations
To create a custom augmentation, implement one of the augmentation interfaces:
- `ISenseAugmentation`: For processing raw data
- `IConduitAugmentation`: For data synchronization
- `ICognitionAugmentation`: For reasoning and inference
- `IMemoryAugmentation`: For data storage
- `IPerceptionAugmentation`: For data interpretation and visualization
- `IDialogAugmentation`: For natural language processing
- `IActivationAugmentation`: For triggering actions
Example:
```javascript
import { AugmentationType, IActivationAugmentation } from '@soulcraft/brainy'
class MyCustomActivation implements IActivationAugmentation {
readonly name = 'my-custom-activation'
readonly description = 'My custom activation augmentation'
enabled = true
getType(): AugmentationType {
return AugmentationType.ACTIVATION
}
async initialize(): Promise<void> {
// Initialization code
}
async shutDown(): Promise<void> {
// Cleanup code
}
async getStatus(): Promise<'active' | 'inactive' | 'error'> {
return 'active'
}
triggerAction(actionName: string, parameters?: Record<string, unknown>): AugmentationResponse<unknown> {
// Implementation
}
generateOutput(knowledgeId: string, format: string): AugmentationResponse<string | Record<string, unknown>> {
// Implementation
}
interactExternal(systemId: string, payload: Record<string, unknown>): AugmentationResponse<unknown> {
// Implementation
}
}
```
## Examples
For complete examples of using augmentations, see the `examples` directory:
- `conduitAugmentationExample.js`: Demonstrates how to use conduit augmentations
- `serverSearchAugmentationExample.js`: Demonstrates how to use server search augmentations

View file

@ -0,0 +1,666 @@
/**
* Server Search Augmentations
*
* This file implements conduit and activation augmentations for browser-server search functionality.
* It allows Brainy to search a server-hosted instance and store results locally.
*/
import {
AugmentationType,
IConduitAugmentation,
IActivationAugmentation,
IWebSocketSupport,
AugmentationResponse,
WebSocketConnection
} from '../types/augmentations.js'
import { WebSocketConduitAugmentation } from './conduitAugmentations.js'
import { v4 as uuidv4 } from 'uuid'
import { BrainyData } from '../brainyData.js'
/**
* ServerSearchConduitAugmentation
*
* A specialized conduit augmentation that provides functionality for searching
* a server-hosted Brainy instance and storing results locally.
*/
export class ServerSearchConduitAugmentation extends WebSocketConduitAugmentation {
private localDb: BrainyData | null = null
constructor(name: string = 'server-search-conduit') {
super(name)
this.description = 'Conduit augmentation for server-hosted Brainy search'
}
/**
* Initialize the augmentation
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
return
}
try {
// Initialize the base conduit
await super.initialize()
// Initialize local Brainy instance if not provided
if (!this.localDb) {
this.localDb = new BrainyData()
await this.localDb.init()
}
this.isInitialized = true
} catch (error) {
console.error(`Failed to initialize ${this.name}:`, error)
throw new Error(`Failed to initialize ${this.name}: ${error}`)
}
}
/**
* Set the local Brainy instance
* @param db The Brainy instance to use for local storage
*/
setLocalDb(db: BrainyData): void {
this.localDb = db
}
/**
* Get the local Brainy instance
* @returns The local Brainy instance
*/
getLocalDb(): BrainyData | null {
return this.localDb
}
/**
* Search the server-hosted Brainy instance and store results locally
* @param connectionId The ID of the established connection
* @param query The search query
* @param limit Maximum number of results to return
* @returns Search results
*/
async searchServer(
connectionId: string,
query: string,
limit: number = 10
): Promise<AugmentationResponse<unknown>> {
await this.ensureInitialized()
try {
// Create a search request
const readResult = await this.readData({
connectionId,
query: {
type: 'search',
query,
limit
}
})
if (readResult.success && readResult.data) {
const searchResults = readResult.data as any[]
// Store the results in the local Brainy instance
if (this.localDb) {
for (const result of searchResults) {
// Check if the noun already exists in the local database
const existingNoun = await this.localDb.get(result.id)
if (!existingNoun) {
// Add the noun to the local database
await this.localDb.add(result.vector, result.metadata)
}
}
}
return {
success: true,
data: searchResults
}
} else {
return {
success: false,
data: null,
error: readResult.error || 'Unknown error searching server'
}
}
} catch (error) {
console.error('Error searching server:', error)
return {
success: false,
data: null,
error: `Error searching server: ${error}`
}
}
}
/**
* Search the local Brainy instance
* @param query The search query
* @param limit Maximum number of results to return
* @returns Search results
*/
async searchLocal(
query: string,
limit: number = 10
): Promise<AugmentationResponse<unknown>> {
await this.ensureInitialized()
try {
if (!this.localDb) {
return {
success: false,
data: null,
error: 'Local database not initialized'
}
}
const results = await this.localDb.searchText(query, limit)
return {
success: true,
data: results
}
} catch (error) {
console.error('Error searching local database:', error)
return {
success: false,
data: null,
error: `Error searching local database: ${error}`
}
}
}
/**
* Search both server and local instances, combine results, and store server results locally
* @param connectionId The ID of the established connection
* @param query The search query
* @param limit Maximum number of results to return
* @returns Combined search results
*/
async searchCombined(
connectionId: string,
query: string,
limit: number = 10
): Promise<AugmentationResponse<unknown>> {
await this.ensureInitialized()
try {
// Search local first
const localSearchResult = await this.searchLocal(query, limit)
if (!localSearchResult.success) {
return localSearchResult
}
const localResults = localSearchResult.data as any[]
// If we have enough local results, return them
if (localResults.length >= limit) {
return localSearchResult
}
// Otherwise, search server for additional results
const serverSearchResult = await this.searchServer(
connectionId,
query,
limit - localResults.length
)
if (!serverSearchResult.success) {
// If server search fails, return local results
return localSearchResult
}
const serverResults = serverSearchResult.data as any[]
// Combine results, removing duplicates
const combinedResults = [...localResults]
const localIds = new Set(localResults.map(r => r.id))
for (const result of serverResults) {
if (!localIds.has(result.id)) {
combinedResults.push(result)
}
}
return {
success: true,
data: combinedResults
}
} catch (error) {
console.error('Error performing combined search:', error)
return {
success: false,
data: null,
error: `Error performing combined search: ${error}`
}
}
}
/**
* Add data to both local and server instances
* @param connectionId The ID of the established connection
* @param data Text or vector to add
* @param metadata Metadata for the data
* @returns ID of the added data
*/
async addToBoth(
connectionId: string,
data: string | any[],
metadata: any = {}
): Promise<AugmentationResponse<string>> {
await this.ensureInitialized()
try {
if (!this.localDb) {
return {
success: false,
data: '',
error: 'Local database not initialized'
}
}
// Add to local first
const id = await this.localDb.add(data, metadata)
// Get the vector and metadata
const noun = await this.localDb.get(id)
if (!noun) {
return {
success: false,
data: '',
error: 'Failed to retrieve newly created noun'
}
}
// Add to server
const writeResult = await this.writeData({
connectionId,
data: {
type: 'addNoun',
vector: noun.vector,
metadata: noun.metadata
}
})
if (!writeResult.success) {
return {
success: true,
data: id,
error: `Added locally but failed to add to server: ${writeResult.error}`
}
}
return {
success: true,
data: id
}
} catch (error) {
console.error('Error adding data to both:', error)
return {
success: false,
data: '',
error: `Error adding data to both: ${error}`
}
}
}
}
/**
* ServerSearchActivationAugmentation
*
* An activation augmentation that provides actions for server search functionality.
*/
export class ServerSearchActivationAugmentation implements IActivationAugmentation {
readonly name: string
readonly description: string
enabled: boolean = true
private isInitialized = false
private conduitAugmentation: ServerSearchConduitAugmentation | null = null
private connections: Map<string, WebSocketConnection> = new Map()
constructor(name: string = 'server-search-activation') {
this.name = name
this.description = 'Activation augmentation for server-hosted Brainy search'
}
getType(): AugmentationType {
return AugmentationType.ACTIVATION
}
/**
* Initialize the augmentation
*/
async initialize(): Promise<void> {
if (this.isInitialized) {
return
}
this.isInitialized = true
}
/**
* Shut down the augmentation
*/
async shutDown(): Promise<void> {
this.isInitialized = false
}
/**
* Get the status of the augmentation
*/
async getStatus(): Promise<'active' | 'inactive' | 'error'> {
return this.isInitialized ? 'active' : 'inactive'
}
/**
* Set the conduit augmentation to use for server search
* @param conduit The ServerSearchConduitAugmentation to use
*/
setConduitAugmentation(conduit: ServerSearchConduitAugmentation): void {
this.conduitAugmentation = conduit
}
/**
* Store a connection for later use
* @param connectionId The ID to use for the connection
* @param connection The WebSocket connection
*/
storeConnection(connectionId: string, connection: WebSocketConnection): void {
this.connections.set(connectionId, connection)
}
/**
* Get a stored connection
* @param connectionId The ID of the connection to retrieve
* @returns The WebSocket connection
*/
getConnection(connectionId: string): WebSocketConnection | undefined {
return this.connections.get(connectionId)
}
/**
* Trigger an action based on a processed command or internal state
* @param actionName The name of the action to trigger
* @param parameters Optional parameters for the action
*/
triggerAction(
actionName: string,
parameters?: Record<string, unknown>
): AugmentationResponse<unknown> {
if (!this.conduitAugmentation) {
return {
success: false,
data: null,
error: 'Conduit augmentation not set'
}
}
// Handle different actions
switch (actionName) {
case 'connectToServer':
return this.handleConnectToServer(parameters || {})
case 'searchServer':
return this.handleSearchServer(parameters || {})
case 'searchLocal':
return this.handleSearchLocal(parameters || {})
case 'searchCombined':
return this.handleSearchCombined(parameters || {})
case 'addToBoth':
return this.handleAddToBoth(parameters || {})
default:
return {
success: false,
data: null,
error: `Unknown action: ${actionName}`
}
}
}
/**
* Handle the connectToServer action
* @param parameters Action parameters
*/
private handleConnectToServer(
parameters: Record<string, unknown>
): AugmentationResponse<unknown> {
const serverUrl = parameters.serverUrl as string
const protocols = parameters.protocols as string | string[] | undefined
if (!serverUrl) {
return {
success: false,
data: null,
error: 'serverUrl parameter is required'
}
}
// Return a promise that will be resolved when the connection is established
return {
success: true,
data: this.conduitAugmentation!.establishConnection(serverUrl, {
protocols
})
}
}
/**
* Handle the searchServer action
* @param parameters Action parameters
*/
private handleSearchServer(
parameters: Record<string, unknown>
): AugmentationResponse<unknown> {
const connectionId = parameters.connectionId as string
const query = parameters.query as string
const limit = parameters.limit as number || 10
if (!connectionId) {
return {
success: false,
data: null,
error: 'connectionId parameter is required'
}
}
if (!query) {
return {
success: false,
data: null,
error: 'query parameter is required'
}
}
// Return a promise that will be resolved when the search is complete
return {
success: true,
data: this.conduitAugmentation!.searchServer(connectionId, query, limit)
}
}
/**
* Handle the searchLocal action
* @param parameters Action parameters
*/
private handleSearchLocal(
parameters: Record<string, unknown>
): AugmentationResponse<unknown> {
const query = parameters.query as string
const limit = parameters.limit as number || 10
if (!query) {
return {
success: false,
data: null,
error: 'query parameter is required'
}
}
// Return a promise that will be resolved when the search is complete
return {
success: true,
data: this.conduitAugmentation!.searchLocal(query, limit)
}
}
/**
* Handle the searchCombined action
* @param parameters Action parameters
*/
private handleSearchCombined(
parameters: Record<string, unknown>
): AugmentationResponse<unknown> {
const connectionId = parameters.connectionId as string
const query = parameters.query as string
const limit = parameters.limit as number || 10
if (!connectionId) {
return {
success: false,
data: null,
error: 'connectionId parameter is required'
}
}
if (!query) {
return {
success: false,
data: null,
error: 'query parameter is required'
}
}
// Return a promise that will be resolved when the search is complete
return {
success: true,
data: this.conduitAugmentation!.searchCombined(connectionId, query, limit)
}
}
/**
* Handle the addToBoth action
* @param parameters Action parameters
*/
private handleAddToBoth(
parameters: Record<string, unknown>
): AugmentationResponse<unknown> {
const connectionId = parameters.connectionId as string
const data = parameters.data
const metadata = parameters.metadata || {}
if (!connectionId) {
return {
success: false,
data: null,
error: 'connectionId parameter is required'
}
}
if (!data) {
return {
success: false,
data: null,
error: 'data parameter is required'
}
}
// Return a promise that will be resolved when the add is complete
return {
success: true,
data: this.conduitAugmentation!.addToBoth(connectionId, data as any, metadata as any)
}
}
/**
* Generates an expressive output or response from Brainy
* @param knowledgeId The identifier of the knowledge to express
* @param format The desired output format (e.g., 'text', 'json')
*/
generateOutput(
knowledgeId: string,
format: string
): AugmentationResponse<string | Record<string, unknown>> {
// This method is not used for server search functionality
return {
success: false,
data: '',
error: 'generateOutput is not implemented for ServerSearchActivationAugmentation'
}
}
/**
* Interacts with an external system or API
* @param systemId The identifier of the external system
* @param payload The data to send to the external system
*/
interactExternal(
systemId: string,
payload: Record<string, unknown>
): AugmentationResponse<unknown> {
// This method is not used for server search functionality
return {
success: false,
data: null,
error: 'interactExternal is not implemented for ServerSearchActivationAugmentation'
}
}
}
/**
* Factory function to create server search augmentations
* @param serverUrl The URL of the server to connect to
* @param options Additional options
* @returns An object containing the created augmentations
*/
export async function createServerSearchAugmentations(
serverUrl: string,
options: {
conduitName?: string,
activationName?: string,
protocols?: string | string[],
localDb?: BrainyData
} = {}
): Promise<{
conduit: ServerSearchConduitAugmentation,
activation: ServerSearchActivationAugmentation,
connection: WebSocketConnection
}> {
// Create the conduit augmentation
const conduit = new ServerSearchConduitAugmentation(options.conduitName)
await conduit.initialize()
// Set the local database if provided
if (options.localDb) {
conduit.setLocalDb(options.localDb)
}
// Create the activation augmentation
const activation = new ServerSearchActivationAugmentation(options.activationName)
await activation.initialize()
// Link the augmentations
activation.setConduitAugmentation(conduit)
// Connect to the server
const connectionResult = await conduit.establishConnection(
serverUrl,
{ protocols: options.protocols }
)
if (!connectionResult.success || !connectionResult.data) {
throw new Error(`Failed to connect to server: ${connectionResult.error}`)
}
const connection = connectionResult.data
// Store the connection in the activation augmentation
activation.storeConnection(connection.connectionId, connection)
return {
conduit,
activation,
connection
}
}