531 lines
18 KiB
JavaScript
531 lines
18 KiB
JavaScript
|
|
/**
|
||
|
|
* 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 } from '../types/augmentations.js';
|
||
|
|
import { WebSocketConduitAugmentation } from './conduitAugmentations.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 {
|
||
|
|
constructor(name = 'server-search-conduit') {
|
||
|
|
super(name);
|
||
|
|
this.localDb = null;
|
||
|
|
// this.description = 'Conduit augmentation for server-hosted Brainy search'
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Initialize the augmentation
|
||
|
|
*/
|
||
|
|
async initialize() {
|
||
|
|
if (this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
// Initialize the base conduit
|
||
|
|
await super.initialize();
|
||
|
|
// Local DB must be set before initialization
|
||
|
|
if (!this.localDb) {
|
||
|
|
throw new Error('Local database not set. Call setLocalDb before initializing.');
|
||
|
|
}
|
||
|
|
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) {
|
||
|
|
this.localDb = db;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the local Brainy instance
|
||
|
|
* @returns The local Brainy instance
|
||
|
|
*/
|
||
|
|
getLocalDb() {
|
||
|
|
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, query, limit = 10) {
|
||
|
|
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;
|
||
|
|
// 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, limit = 10) {
|
||
|
|
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, query, limit = 10) {
|
||
|
|
await this.ensureInitialized();
|
||
|
|
try {
|
||
|
|
// Search local first
|
||
|
|
const localSearchResult = await this.searchLocal(query, limit);
|
||
|
|
if (!localSearchResult.success) {
|
||
|
|
return localSearchResult;
|
||
|
|
}
|
||
|
|
const localResults = localSearchResult.data;
|
||
|
|
// 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;
|
||
|
|
// 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, data, metadata = {}) {
|
||
|
|
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 {
|
||
|
|
constructor(name = 'server-search-activation') {
|
||
|
|
this.enabled = true;
|
||
|
|
this.isInitialized = false;
|
||
|
|
this.conduitAugmentation = null;
|
||
|
|
this.connections = new Map();
|
||
|
|
this.name = name;
|
||
|
|
this.description = 'Activation augmentation for server-hosted Brainy search';
|
||
|
|
}
|
||
|
|
getType() {
|
||
|
|
return AugmentationType.ACTIVATION;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Initialize the augmentation
|
||
|
|
*/
|
||
|
|
async initialize() {
|
||
|
|
if (this.isInitialized) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.isInitialized = true;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Shut down the augmentation
|
||
|
|
*/
|
||
|
|
async shutDown() {
|
||
|
|
this.isInitialized = false;
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get the status of the augmentation
|
||
|
|
*/
|
||
|
|
async getStatus() {
|
||
|
|
return this.isInitialized ? 'active' : 'inactive';
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Set the conduit augmentation to use for server search
|
||
|
|
* @param conduit The ServerSearchConduitAugmentation to use
|
||
|
|
*/
|
||
|
|
setConduitAugmentation(conduit) {
|
||
|
|
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, connection) {
|
||
|
|
this.connections.set(connectionId, connection);
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* Get a stored connection
|
||
|
|
* @param connectionId The ID of the connection to retrieve
|
||
|
|
* @returns The WebSocket connection
|
||
|
|
*/
|
||
|
|
getConnection(connectionId) {
|
||
|
|
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, parameters) {
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
handleConnectToServer(parameters) {
|
||
|
|
const serverUrl = parameters.serverUrl;
|
||
|
|
const protocols = parameters.protocols;
|
||
|
|
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
|
||
|
|
*/
|
||
|
|
handleSearchServer(parameters) {
|
||
|
|
const connectionId = parameters.connectionId;
|
||
|
|
const query = parameters.query;
|
||
|
|
const limit = parameters.limit || 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
|
||
|
|
*/
|
||
|
|
handleSearchLocal(parameters) {
|
||
|
|
const query = parameters.query;
|
||
|
|
const limit = parameters.limit || 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
|
||
|
|
*/
|
||
|
|
handleSearchCombined(parameters) {
|
||
|
|
const connectionId = parameters.connectionId;
|
||
|
|
const query = parameters.query;
|
||
|
|
const limit = parameters.limit || 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
|
||
|
|
*/
|
||
|
|
handleAddToBoth(parameters) {
|
||
|
|
const connectionId = parameters.connectionId;
|
||
|
|
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, metadata)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
/**
|
||
|
|
* 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, format) {
|
||
|
|
// 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, payload) {
|
||
|
|
// 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, options = {}) {
|
||
|
|
// 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
|
||
|
|
};
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=serverSearchAugmentations.js.map
|