feat: add browser-server search example with server search augmentations
This commit is contained in:
parent
bf3d9a055b
commit
a206b9926d
7 changed files with 2021 additions and 0 deletions
63
examples/README.md
Normal file
63
examples/README.md
Normal 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!
|
||||
187
examples/browser-server-search/README.md
Normal file
187
examples/browser-server-search/README.md
Normal 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
|
||||
251
examples/browser-server-search/index.html
Normal file
251
examples/browser-server-search/index.html
Normal 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>
|
||||
278
examples/browser-server-search/index.js
Normal file
278
examples/browser-server-search/index.js
Normal 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 };
|
||||
346
examples/serverSearchAugmentationExample.js
Normal file
346
examples/serverSearchAugmentationExample.js
Normal 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
230
src/augmentations/README.md
Normal 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
|
||||
666
src/augmentations/serverSearchAugmentations.ts
Normal file
666
src/augmentations/serverSearchAugmentations.ts
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue