feat: add Universal Display Augmentation for AI-powered enhanced output
- Implements intelligent display fields with AI-generated titles and descriptions - Leverages existing IntelligentTypeMatcher for semantic type detection - Adds lazy computation with LRU caching for zero performance impact - Enhances CLI with clean, minimal formatting (no visual clutter) - Provides method-based API (getDisplay()) to avoid namespace conflicts - Maintains 100% backward compatibility with existing code - Enables by default with complete isolation architecture - Includes comprehensive tests and documentation The augmentation transforms search results and data display with smart, contextual information while maintaining Soulcraft's clean aesthetic.
This commit is contained in:
parent
99c11385ab
commit
b409075d0b
11 changed files with 3511 additions and 62 deletions
|
|
@ -109,6 +109,30 @@ export interface BrainyAugmentation {
|
|||
* Optional: Cleanup when BrainyData is destroyed
|
||||
*/
|
||||
shutdown?(): Promise<void>
|
||||
|
||||
/**
|
||||
* Optional: Computed fields this augmentation provides
|
||||
* Used for discovery, TypeScript support, and API documentation
|
||||
*/
|
||||
computedFields?: {
|
||||
[namespace: string]: {
|
||||
[field: string]: {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array'
|
||||
description: string
|
||||
confidence?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional: Compute fields for a result entity
|
||||
* Called when user accesses getDisplay(), getSchema(), etc.
|
||||
*
|
||||
* @param result - The result entity (VectorDocument, GraphVerb, etc.)
|
||||
* @param namespace - The namespace being requested ('display', 'schema', etc.)
|
||||
* @returns Computed fields for the namespace
|
||||
*/
|
||||
computeFields?(result: any, namespace: string): Promise<Record<string, any>> | Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -205,6 +229,27 @@ export abstract class BaseAugmentation implements BrainyAugmentation {
|
|||
// Default: no-op
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional computed fields declaration (override in subclasses)
|
||||
*/
|
||||
computedFields?: {
|
||||
[namespace: string]: {
|
||||
[field: string]: {
|
||||
type: 'string' | 'number' | 'boolean' | 'object' | 'array'
|
||||
description: string
|
||||
confidence?: number
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional computed fields implementation (override in subclasses)
|
||||
* @param result The result entity
|
||||
* @param namespace The requested namespace
|
||||
* @returns Computed fields for the namespace
|
||||
*/
|
||||
computeFields?(result: any, namespace: string): Promise<Record<string, any>> | Record<string, any>
|
||||
|
||||
/**
|
||||
* Log a message with the augmentation name
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { CacheAugmentation } from './cacheAugmentation.js'
|
|||
import { IndexAugmentation } from './indexAugmentation.js'
|
||||
import { MetricsAugmentation } from './metricsAugmentation.js'
|
||||
import { MonitoringAugmentation } from './monitoringAugmentation.js'
|
||||
import { UniversalDisplayAugmentation } from './universalDisplayAugmentation.js'
|
||||
|
||||
/**
|
||||
* Create default augmentations for zero-config operation
|
||||
|
|
@ -28,6 +29,7 @@ export function createDefaultAugmentations(
|
|||
index?: boolean | Record<string, any>
|
||||
metrics?: boolean | Record<string, any>
|
||||
monitoring?: boolean | Record<string, any>
|
||||
display?: boolean | Record<string, any>
|
||||
} = {}
|
||||
): BaseAugmentation[] {
|
||||
const augmentations: BaseAugmentation[] = []
|
||||
|
|
@ -50,6 +52,12 @@ export function createDefaultAugmentations(
|
|||
augmentations.push(new MetricsAugmentation(metricsConfig))
|
||||
}
|
||||
|
||||
// Display augmentation (AI-powered intelligent display fields)
|
||||
if (config.display !== false) {
|
||||
const displayConfig = typeof config.display === 'object' ? config.display : {}
|
||||
augmentations.push(new UniversalDisplayAugmentation(displayConfig))
|
||||
}
|
||||
|
||||
// Monitoring augmentation (was HealthMonitor)
|
||||
// Only enable by default in distributed mode
|
||||
const isDistributed = process.env.BRAINY_MODE === 'distributed' ||
|
||||
|
|
@ -104,5 +112,12 @@ export const AugmentationHelpers = {
|
|||
*/
|
||||
getMonitoring(brain: BrainyData): MonitoringAugmentation | null {
|
||||
return getAugmentation<MonitoringAugmentation>(brain, 'monitoring')
|
||||
},
|
||||
|
||||
/**
|
||||
* Get display augmentation
|
||||
*/
|
||||
getDisplay(brain: BrainyData): UniversalDisplayAugmentation | null {
|
||||
return getAugmentation<UniversalDisplayAugmentation>(brain, 'display')
|
||||
}
|
||||
}
|
||||
375
src/augmentations/display/cache.ts
Normal file
375
src/augmentations/display/cache.ts
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Caching System
|
||||
*
|
||||
* High-performance LRU cache with smart eviction and batch optimization
|
||||
* Designed for minimal memory footprint and maximum hit ratio
|
||||
*/
|
||||
|
||||
import type { DisplayCacheEntry, ComputedDisplayFields, DisplayAugmentationStats } from './types.js'
|
||||
|
||||
/**
|
||||
* LRU (Least Recently Used) Cache for computed display fields
|
||||
* Optimized for the display augmentation use case
|
||||
*/
|
||||
export class DisplayCache {
|
||||
private cache = new Map<string, DisplayCacheEntry>()
|
||||
private readonly maxSize: number
|
||||
private stats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
evictions: 0,
|
||||
totalComputations: 0,
|
||||
totalComputationTime: 0
|
||||
}
|
||||
|
||||
constructor(maxSize: number = 1000) {
|
||||
this.maxSize = maxSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cached display fields with LRU update
|
||||
* @param key Cache key
|
||||
* @returns Cached fields or null if not found
|
||||
*/
|
||||
get(key: string): ComputedDisplayFields | null {
|
||||
const entry = this.cache.get(key)
|
||||
|
||||
if (!entry) {
|
||||
this.stats.misses++
|
||||
return null
|
||||
}
|
||||
|
||||
// Update LRU - move to end
|
||||
this.cache.delete(key)
|
||||
entry.lastAccessed = Date.now()
|
||||
entry.accessCount++
|
||||
this.cache.set(key, entry)
|
||||
|
||||
this.stats.hits++
|
||||
return entry.fields
|
||||
}
|
||||
|
||||
/**
|
||||
* Store computed display fields in cache
|
||||
* @param key Cache key
|
||||
* @param fields Computed display fields
|
||||
* @param computationTime Time taken to compute (for stats)
|
||||
*/
|
||||
set(key: string, fields: ComputedDisplayFields, computationTime?: number): void {
|
||||
// Remove if already exists (for LRU update)
|
||||
if (this.cache.has(key)) {
|
||||
this.cache.delete(key)
|
||||
}
|
||||
|
||||
// Create cache entry
|
||||
const entry: DisplayCacheEntry = {
|
||||
fields,
|
||||
lastAccessed: Date.now(),
|
||||
accessCount: 1
|
||||
}
|
||||
|
||||
// Add to end (most recently used)
|
||||
this.cache.set(key, entry)
|
||||
|
||||
// Update stats
|
||||
this.stats.totalComputations++
|
||||
if (computationTime) {
|
||||
this.stats.totalComputationTime += computationTime
|
||||
}
|
||||
|
||||
// Evict oldest if over capacity
|
||||
if (this.cache.size > this.maxSize) {
|
||||
this.evictOldest()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a key exists in cache without affecting LRU order
|
||||
* @param key Cache key
|
||||
* @returns True if key exists
|
||||
*/
|
||||
has(key: string): boolean {
|
||||
return this.cache.has(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate cache key from data
|
||||
* @param id Entity ID (preferred)
|
||||
* @param data Fallback data for key generation
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @returns Cache key string
|
||||
*/
|
||||
generateKey(id?: string, data?: any, entityType: 'noun' | 'verb' = 'noun'): string {
|
||||
// Use ID if available (most reliable)
|
||||
if (id) {
|
||||
return `${entityType}:${id}`
|
||||
}
|
||||
|
||||
// Generate hash from data
|
||||
if (data) {
|
||||
const dataString = JSON.stringify(data, Object.keys(data).sort())
|
||||
const hash = this.simpleHash(dataString)
|
||||
return `${entityType}:hash:${hash}`
|
||||
}
|
||||
|
||||
// Fallback to timestamp (not ideal but prevents crashes)
|
||||
return `${entityType}:temp:${Date.now()}:${Math.random()}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached entries
|
||||
*/
|
||||
clear(): void {
|
||||
this.cache.clear()
|
||||
this.stats = {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
evictions: 0,
|
||||
totalComputations: 0,
|
||||
totalComputationTime: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache statistics
|
||||
* @returns Cache performance statistics
|
||||
*/
|
||||
getStats(): DisplayAugmentationStats {
|
||||
const hitRatio = this.stats.hits + this.stats.misses > 0
|
||||
? this.stats.hits / (this.stats.hits + this.stats.misses)
|
||||
: 0
|
||||
|
||||
const avgComputationTime = this.stats.totalComputations > 0
|
||||
? this.stats.totalComputationTime / this.stats.totalComputations
|
||||
: 0
|
||||
|
||||
// Analyze cached types for common types statistics
|
||||
const typeCount = new Map<string, number>()
|
||||
let fastestComputation = Infinity
|
||||
let slowestComputation = 0
|
||||
|
||||
for (const entry of this.cache.values()) {
|
||||
const type = entry.fields.type
|
||||
typeCount.set(type, (typeCount.get(type) || 0) + 1)
|
||||
}
|
||||
|
||||
const commonTypes = Array.from(typeCount.entries())
|
||||
.sort(([,a], [,b]) => b - a)
|
||||
.slice(0, 10)
|
||||
.map(([type, count]) => ({
|
||||
type,
|
||||
count,
|
||||
percentage: Math.round((count / this.cache.size) * 100)
|
||||
}))
|
||||
|
||||
return {
|
||||
totalComputations: this.stats.totalComputations,
|
||||
cacheHitRatio: Math.round(hitRatio * 100) / 100,
|
||||
averageComputationTime: Math.round(avgComputationTime * 100) / 100,
|
||||
commonTypes,
|
||||
performance: {
|
||||
fastestComputation,
|
||||
slowestComputation,
|
||||
totalComputationTime: this.stats.totalComputationTime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current cache size
|
||||
* @returns Number of cached entries
|
||||
*/
|
||||
size(): number {
|
||||
return this.cache.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache capacity
|
||||
* @returns Maximum cache size
|
||||
*/
|
||||
capacity(): number {
|
||||
return this.maxSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Evict least recently used entry
|
||||
*/
|
||||
private evictOldest(): void {
|
||||
// First entry is oldest (LRU)
|
||||
const firstKey = this.cache.keys().next().value
|
||||
if (firstKey) {
|
||||
this.cache.delete(firstKey)
|
||||
this.stats.evictions++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple hash function for cache keys
|
||||
* @param str String to hash
|
||||
* @returns Simple hash number
|
||||
*/
|
||||
private simpleHash(str: string): number {
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i)
|
||||
hash = ((hash << 5) - hash) + char
|
||||
hash = hash & hash // Convert to 32-bit integer
|
||||
}
|
||||
return Math.abs(hash)
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize cache by removing stale entries
|
||||
* Called periodically to maintain cache health
|
||||
*/
|
||||
optimizeCache(): void {
|
||||
const now = Date.now()
|
||||
const maxAge = 24 * 60 * 60 * 1000 // 24 hours
|
||||
const minAccessCount = 2 // Minimum access count to keep
|
||||
|
||||
const toDelete: string[] = []
|
||||
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
// Remove very old entries with low access count
|
||||
if (now - entry.lastAccessed > maxAge && entry.accessCount < minAccessCount) {
|
||||
toDelete.push(key)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stale entries
|
||||
for (const key of toDelete) {
|
||||
this.cache.delete(key)
|
||||
this.stats.evictions++
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Precompute display fields for a batch of entities
|
||||
* @param entities Array of entities with their compute functions
|
||||
* @returns Promise resolving when batch is complete
|
||||
*/
|
||||
async batchPrecompute<T>(
|
||||
entities: Array<{
|
||||
key: string
|
||||
computeFn: () => Promise<ComputedDisplayFields>
|
||||
}>
|
||||
): Promise<void> {
|
||||
const promises = entities.map(async ({ key, computeFn }) => {
|
||||
if (!this.has(key)) {
|
||||
const startTime = Date.now()
|
||||
try {
|
||||
const fields = await computeFn()
|
||||
const computationTime = Date.now() - startTime
|
||||
this.set(key, fields, computationTime)
|
||||
} catch (error) {
|
||||
console.warn(`Batch precompute failed for key ${key}:`, error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request deduplicator for batch processing
|
||||
* Prevents duplicate computations for the same data
|
||||
*/
|
||||
export class RequestDeduplicator {
|
||||
private pendingRequests = new Map<string, Promise<ComputedDisplayFields>>()
|
||||
private readonly batchSize: number
|
||||
|
||||
constructor(batchSize: number = 50) {
|
||||
this.batchSize = batchSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Deduplicate computation request
|
||||
* @param key Unique key for the computation
|
||||
* @param computeFn Function to compute the result
|
||||
* @returns Promise that resolves to the computed fields
|
||||
*/
|
||||
async deduplicate(
|
||||
key: string,
|
||||
computeFn: () => Promise<ComputedDisplayFields>
|
||||
): Promise<ComputedDisplayFields> {
|
||||
// Return existing promise if already pending
|
||||
if (this.pendingRequests.has(key)) {
|
||||
return this.pendingRequests.get(key)!
|
||||
}
|
||||
|
||||
// Create new computation promise
|
||||
const promise = computeFn().finally(() => {
|
||||
// Remove from pending when complete
|
||||
this.pendingRequests.delete(key)
|
||||
})
|
||||
|
||||
this.pendingRequests.set(key, promise)
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* Get number of pending requests
|
||||
* @returns Number of pending computations
|
||||
*/
|
||||
getPendingCount(): number {
|
||||
return this.pendingRequests.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all pending requests
|
||||
*/
|
||||
clear(): void {
|
||||
this.pendingRequests.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the deduplicator
|
||||
*/
|
||||
shutdown(): void {
|
||||
this.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Global cache instance management
|
||||
* Provides singleton access to display cache
|
||||
*/
|
||||
let globalDisplayCache: DisplayCache | null = null
|
||||
|
||||
/**
|
||||
* Get global display cache instance
|
||||
* @param maxSize Optional cache size (only used on first call)
|
||||
* @returns Shared display cache instance
|
||||
*/
|
||||
export function getGlobalDisplayCache(maxSize?: number): DisplayCache {
|
||||
if (!globalDisplayCache) {
|
||||
globalDisplayCache = new DisplayCache(maxSize)
|
||||
|
||||
// Set up periodic optimization
|
||||
setInterval(() => {
|
||||
globalDisplayCache?.optimizeCache()
|
||||
}, 60 * 60 * 1000) // Every hour
|
||||
}
|
||||
|
||||
return globalDisplayCache
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear global cache (for testing or memory management)
|
||||
*/
|
||||
export function clearGlobalDisplayCache(): void {
|
||||
if (globalDisplayCache) {
|
||||
globalDisplayCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown global cache and cleanup
|
||||
*/
|
||||
export function shutdownGlobalDisplayCache(): void {
|
||||
if (globalDisplayCache) {
|
||||
globalDisplayCache.clear()
|
||||
globalDisplayCache = null
|
||||
}
|
||||
}
|
||||
458
src/augmentations/display/fieldPatterns.ts
Normal file
458
src/augmentations/display/fieldPatterns.ts
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Smart Field Patterns
|
||||
*
|
||||
* Intelligent field detection patterns for mapping user data to display fields
|
||||
* Uses semantic understanding and common naming conventions
|
||||
*/
|
||||
|
||||
import type { FieldPattern, FieldComputationContext } from './types.js'
|
||||
import { NounType, VerbType } from '../../types/graphTypes.js'
|
||||
|
||||
/**
|
||||
* Universal field patterns that work across all data types
|
||||
* Ordered by confidence level (highest first)
|
||||
*/
|
||||
export const UNIVERSAL_FIELD_PATTERNS: FieldPattern[] = [
|
||||
// Title/Name Patterns (Highest Priority)
|
||||
{
|
||||
fields: ['name', 'title', 'displayName', 'label', 'heading'],
|
||||
displayField: 'title',
|
||||
confidence: 0.95
|
||||
},
|
||||
{
|
||||
fields: ['firstName', 'lastName', 'fullName', 'realName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Person, NounType.User],
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
if (metadata.firstName && metadata.lastName) {
|
||||
return `${metadata.firstName} ${metadata.lastName}`.trim()
|
||||
}
|
||||
return String(value || '')
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['companyName', 'organizationName', 'orgName', 'businessName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Organization]
|
||||
},
|
||||
{
|
||||
fields: ['filename', 'fileName', 'documentTitle', 'docName'],
|
||||
displayField: 'title',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Document, NounType.File, NounType.Media]
|
||||
},
|
||||
{
|
||||
fields: ['projectName', 'projectTitle', 'initiative'],
|
||||
displayField: 'title',
|
||||
confidence: 0.9,
|
||||
applicableTypes: [NounType.Project]
|
||||
},
|
||||
{
|
||||
fields: ['taskName', 'taskTitle', 'action', 'todo'],
|
||||
displayField: 'title',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Task]
|
||||
},
|
||||
{
|
||||
fields: ['subject', 'topic', 'headline', 'caption'],
|
||||
displayField: 'title',
|
||||
confidence: 0.8
|
||||
},
|
||||
|
||||
// Description Patterns (High Priority)
|
||||
{
|
||||
fields: ['description', 'summary', 'overview', 'details'],
|
||||
displayField: 'description',
|
||||
confidence: 0.9
|
||||
},
|
||||
{
|
||||
fields: ['bio', 'biography', 'profile', 'about'],
|
||||
displayField: 'description',
|
||||
confidence: 0.85,
|
||||
applicableTypes: [NounType.Person, NounType.User]
|
||||
},
|
||||
{
|
||||
fields: ['content', 'text', 'body', 'message'],
|
||||
displayField: 'description',
|
||||
confidence: 0.8
|
||||
},
|
||||
{
|
||||
fields: ['abstract', 'excerpt', 'snippet', 'preview'],
|
||||
displayField: 'description',
|
||||
confidence: 0.75
|
||||
},
|
||||
{
|
||||
fields: ['notes', 'comments', 'remarks', 'observations'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7
|
||||
},
|
||||
|
||||
// Type Patterns (Medium Priority)
|
||||
{
|
||||
fields: ['type', 'category', 'classification', 'kind'],
|
||||
displayField: 'type',
|
||||
confidence: 0.9
|
||||
},
|
||||
{
|
||||
fields: ['nounType', 'entityType', 'objectType'],
|
||||
displayField: 'type',
|
||||
confidence: 0.95
|
||||
},
|
||||
{
|
||||
fields: ['role', 'position', 'jobTitle', 'occupation'],
|
||||
displayField: 'type',
|
||||
confidence: 0.8,
|
||||
applicableTypes: [NounType.Person, NounType.User],
|
||||
transform: (value: any) => String(value || 'Person')
|
||||
},
|
||||
{
|
||||
fields: ['industry', 'sector', 'domain', 'field'],
|
||||
displayField: 'type',
|
||||
confidence: 0.7,
|
||||
applicableTypes: [NounType.Organization]
|
||||
},
|
||||
|
||||
// Tag Patterns (Medium Priority)
|
||||
{
|
||||
fields: ['tags', 'keywords', 'labels', 'categories'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.85,
|
||||
transform: (value: any) => {
|
||||
if (Array.isArray(value)) return value
|
||||
if (typeof value === 'string') {
|
||||
// Handle comma-separated, semicolon-separated, or space-separated tags
|
||||
return value.split(/[,;]\s*|\s+/).filter(Boolean)
|
||||
}
|
||||
return []
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['topics', 'subjects', 'themes'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.8,
|
||||
transform: (value: any) => Array.isArray(value) ? value : [String(value || '')]
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* Type-specific field patterns for enhanced detection
|
||||
* Used when we know the specific type of the entity
|
||||
*/
|
||||
export const TYPE_SPECIFIC_PATTERNS: Record<string, FieldPattern[]> = {
|
||||
[NounType.Person]: [
|
||||
{
|
||||
fields: ['email', 'emailAddress', 'contactEmail'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
const role = metadata.role || metadata.jobTitle || metadata.position
|
||||
const company = metadata.company || metadata.organization || metadata.employer
|
||||
|
||||
const parts = []
|
||||
if (role) parts.push(role)
|
||||
if (company) parts.push(`at ${company}`)
|
||||
if (parts.length === 0 && value) parts.push(`Contact: ${value}`)
|
||||
|
||||
return parts.join(' ') || 'Person'
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['phone', 'phoneNumber', 'mobile', 'cell'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.6,
|
||||
transform: () => ['contact', 'person']
|
||||
}
|
||||
],
|
||||
|
||||
[NounType.Organization]: [
|
||||
{
|
||||
fields: ['website', 'url', 'homepage', 'domain'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
const industry = metadata.industry || metadata.sector
|
||||
const location = metadata.location || metadata.city || metadata.country
|
||||
|
||||
const parts = []
|
||||
if (industry) parts.push(industry)
|
||||
parts.push('organization')
|
||||
if (location) parts.push(`in ${location}`)
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
},
|
||||
{
|
||||
fields: ['employees', 'size', 'headcount'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.6,
|
||||
transform: (value: any) => {
|
||||
const size = parseInt(String(value || '0'))
|
||||
if (size > 10000) return ['enterprise', 'large']
|
||||
if (size > 1000) return ['large', 'corporation']
|
||||
if (size > 100) return ['medium', 'company']
|
||||
if (size > 10) return ['small', 'business']
|
||||
return ['startup', 'small']
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
[NounType.Project]: [
|
||||
{
|
||||
fields: ['status', 'phase', 'stage', 'state'],
|
||||
displayField: 'description',
|
||||
confidence: 0.8,
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
const status = String(value || 'active').toLowerCase()
|
||||
const budget = metadata.budget || metadata.cost
|
||||
const lead = metadata.lead || metadata.manager || metadata.owner
|
||||
|
||||
const parts = []
|
||||
parts.push(status.charAt(0).toUpperCase() + status.slice(1))
|
||||
if (metadata.description) parts.push('project')
|
||||
if (lead) parts.push(`led by ${lead}`)
|
||||
if (budget) parts.push(`($${parseInt(String(budget)).toLocaleString()} budget)`)
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
[NounType.Document]: [
|
||||
{
|
||||
fields: ['author', 'creator', 'writer'],
|
||||
displayField: 'description',
|
||||
confidence: 0.7,
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
const docType = metadata.type || metadata.category || 'document'
|
||||
const date = metadata.date || metadata.created || metadata.published
|
||||
|
||||
const parts = []
|
||||
if (docType) parts.push(docType)
|
||||
if (value) parts.push(`by ${value}`)
|
||||
if (date) {
|
||||
const dateStr = new Date(date).toLocaleDateString()
|
||||
parts.push(`(${dateStr})`)
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
[NounType.Task]: [
|
||||
{
|
||||
fields: ['priority', 'urgency', 'importance'],
|
||||
displayField: 'tags',
|
||||
confidence: 0.7,
|
||||
transform: (value: any, context: FieldComputationContext) => {
|
||||
const { metadata } = context
|
||||
const tags = ['task']
|
||||
const priority = String(value || 'medium').toLowerCase()
|
||||
|
||||
tags.push(priority)
|
||||
if (metadata.status) tags.push(String(metadata.status).toLowerCase())
|
||||
if (metadata.assignee) tags.push('assigned')
|
||||
|
||||
return tags
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field patterns for a specific entity type
|
||||
* @param entityType The type of entity (noun or verb)
|
||||
* @param specificType Optional specific noun/verb type
|
||||
* @returns Array of applicable field patterns
|
||||
*/
|
||||
export function getFieldPatterns(entityType: 'noun' | 'verb', specificType?: string): FieldPattern[] {
|
||||
const patterns = [...UNIVERSAL_FIELD_PATTERNS]
|
||||
|
||||
if (entityType === 'noun' && specificType && TYPE_SPECIFIC_PATTERNS[specificType]) {
|
||||
patterns.unshift(...TYPE_SPECIFIC_PATTERNS[specificType])
|
||||
}
|
||||
|
||||
return patterns.sort((a, b) => b.confidence - a.confidence)
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority fields for different entity types (for AI analysis)
|
||||
* Used by the IntelligentTypeMatcher and neural processing
|
||||
*/
|
||||
export const TYPE_PRIORITY_FIELDS: Record<string, string[]> = {
|
||||
[NounType.Person]: [
|
||||
'name', 'firstName', 'lastName', 'fullName', 'displayName',
|
||||
'email', 'role', 'jobTitle', 'position', 'title',
|
||||
'bio', 'description', 'about', 'profile',
|
||||
'company', 'organization', 'employer'
|
||||
],
|
||||
|
||||
[NounType.Organization]: [
|
||||
'name', 'companyName', 'organizationName', 'title',
|
||||
'industry', 'sector', 'domain', 'type',
|
||||
'description', 'about', 'summary',
|
||||
'location', 'city', 'country', 'headquarters',
|
||||
'website', 'url'
|
||||
],
|
||||
|
||||
[NounType.Project]: [
|
||||
'name', 'projectName', 'title', 'projectTitle',
|
||||
'description', 'summary', 'overview', 'goal',
|
||||
'status', 'phase', 'stage', 'state',
|
||||
'lead', 'manager', 'owner', 'team',
|
||||
'budget', 'timeline', 'deadline'
|
||||
],
|
||||
|
||||
[NounType.Document]: [
|
||||
'title', 'filename', 'name', 'subject',
|
||||
'content', 'text', 'body', 'summary',
|
||||
'author', 'creator', 'writer',
|
||||
'type', 'category', 'format',
|
||||
'date', 'created', 'published'
|
||||
],
|
||||
|
||||
[NounType.Task]: [
|
||||
'title', 'name', 'taskName', 'action',
|
||||
'description', 'details', 'notes',
|
||||
'status', 'state', 'priority',
|
||||
'assignee', 'owner', 'responsible',
|
||||
'due', 'deadline', 'dueDate'
|
||||
],
|
||||
|
||||
[NounType.Event]: [
|
||||
'name', 'title', 'eventName',
|
||||
'description', 'details', 'summary',
|
||||
'startDate', 'endDate', 'date', 'time',
|
||||
'location', 'venue', 'address',
|
||||
'organizer', 'host', 'creator'
|
||||
],
|
||||
|
||||
[NounType.Product]: [
|
||||
'name', 'productName', 'title',
|
||||
'description', 'summary', 'features',
|
||||
'price', 'cost', 'value',
|
||||
'category', 'type', 'brand',
|
||||
'manufacturer', 'vendor'
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get priority fields for intelligent analysis
|
||||
* @param entityType The type of entity
|
||||
* @param specificType Optional specific type
|
||||
* @returns Array of priority field names
|
||||
*/
|
||||
export function getPriorityFields(entityType: 'noun' | 'verb', specificType?: string): string[] {
|
||||
if (entityType === 'noun' && specificType && TYPE_PRIORITY_FIELDS[specificType]) {
|
||||
return TYPE_PRIORITY_FIELDS[specificType]
|
||||
}
|
||||
|
||||
// Default priority fields for any entity
|
||||
return [
|
||||
'name', 'title', 'label', 'displayName',
|
||||
'description', 'summary', 'about', 'details',
|
||||
'type', 'category', 'kind', 'classification',
|
||||
'tags', 'keywords', 'labels'
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart field value extraction with type-aware processing
|
||||
* @param data The data object to extract from
|
||||
* @param pattern The field pattern to apply
|
||||
* @param context The computation context
|
||||
* @returns The extracted and processed field value
|
||||
*/
|
||||
export function extractFieldValue(
|
||||
data: any,
|
||||
pattern: FieldPattern,
|
||||
context: FieldComputationContext
|
||||
): any {
|
||||
// Find the first matching field
|
||||
let value: any = null
|
||||
let matchedField: string | null = null
|
||||
|
||||
for (const field of pattern.fields) {
|
||||
if (data[field] !== undefined && data[field] !== null && data[field] !== '') {
|
||||
value = data[field]
|
||||
matchedField = field
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (value === null) return null
|
||||
|
||||
// Apply transformation if provided
|
||||
if (pattern.transform) {
|
||||
try {
|
||||
return pattern.transform(value, context)
|
||||
} catch (error) {
|
||||
console.warn(`Field transformation error for ${matchedField}:`, error)
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Default processing based on display field type
|
||||
switch (pattern.displayField) {
|
||||
case 'title':
|
||||
case 'description':
|
||||
case 'type':
|
||||
return String(value)
|
||||
|
||||
case 'tags':
|
||||
if (Array.isArray(value)) return value
|
||||
if (typeof value === 'string') {
|
||||
return value.split(/[,;]\s*|\s+/).filter(Boolean)
|
||||
}
|
||||
return [String(value)]
|
||||
|
||||
default:
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate confidence score for field detection
|
||||
* @param pattern The field pattern
|
||||
* @param context The computation context
|
||||
* @param value The extracted value
|
||||
* @returns Confidence score (0-1)
|
||||
*/
|
||||
export function calculateFieldConfidence(
|
||||
pattern: FieldPattern,
|
||||
context: FieldComputationContext,
|
||||
value: any
|
||||
): number {
|
||||
let confidence = pattern.confidence
|
||||
|
||||
// Boost confidence if type matches
|
||||
if (pattern.applicableTypes && context.typeResult) {
|
||||
if (pattern.applicableTypes.includes(context.typeResult.type)) {
|
||||
confidence = Math.min(1.0, confidence + 0.1)
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce confidence for empty or very short values
|
||||
if (typeof value === 'string') {
|
||||
if (value.length < 2) {
|
||||
confidence *= 0.5
|
||||
} else if (value.length < 5) {
|
||||
confidence *= 0.8
|
||||
}
|
||||
}
|
||||
|
||||
// Reduce confidence for generic values
|
||||
const genericValues = ['unknown', 'n/a', 'null', 'undefined', 'default']
|
||||
if (typeof value === 'string' && genericValues.includes(value.toLowerCase())) {
|
||||
confidence *= 0.3
|
||||
}
|
||||
|
||||
return Math.max(0, Math.min(1, confidence))
|
||||
}
|
||||
76
src/augmentations/display/iconMappings.ts
Normal file
76
src/augmentations/display/iconMappings.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Clean Display
|
||||
*
|
||||
* Simple, clean display without icons - focusing on AI-powered
|
||||
* titles, descriptions, and smart formatting that matches
|
||||
* Soulcraft's minimal aesthetic
|
||||
*/
|
||||
|
||||
import { NounType, VerbType } from '../../types/graphTypes.js'
|
||||
|
||||
/**
|
||||
* No icon mappings - clean, minimal approach
|
||||
* The real value is in AI-generated titles and enhanced descriptions,
|
||||
* not visual clutter that doesn't align with professional aesthetics
|
||||
*/
|
||||
export const NOUN_TYPE_ICONS: Record<string, string> = {}
|
||||
|
||||
/**
|
||||
* No icon mappings for verbs either - focus on clear relationship descriptions
|
||||
* Human-readable relationship text is more valuable than symbolic representations
|
||||
*/
|
||||
export const VERB_TYPE_ICONS: Record<string, string> = {}
|
||||
|
||||
/**
|
||||
* Get icon for a noun type (returns empty string for clean display)
|
||||
* @param type The noun type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export function getNounIcon(type: string): string {
|
||||
return '' // Clean, no icons
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon for a verb type (returns empty string for clean display)
|
||||
* @param type The verb type
|
||||
* @returns Empty string (no icons)
|
||||
*/
|
||||
export function getVerbIcon(type: string): string {
|
||||
return '' // Clean, no icons
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coverage statistics (for backwards compatibility)
|
||||
* @returns Coverage info showing clean approach
|
||||
*/
|
||||
export function getIconCoverage() {
|
||||
return {
|
||||
nounTypes: {
|
||||
total: 'Clean display - no icons needed',
|
||||
covered: 'Focus on AI-powered content'
|
||||
},
|
||||
verbTypes: {
|
||||
total: 'Clean display - no icons needed',
|
||||
covered: 'Focus on relationship descriptions'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an icon exists for a type (always false for clean display)
|
||||
* @param type The type to check
|
||||
* @param entityType Whether it's a noun or verb
|
||||
* @returns Always false (no icons)
|
||||
*/
|
||||
export function hasIcon(type: string, entityType: 'noun' | 'verb' = 'noun'): boolean {
|
||||
return false // Clean approach - no icons
|
||||
}
|
||||
|
||||
/**
|
||||
* Get fallback icon (returns empty string for clean display)
|
||||
* @param entityType The entity type
|
||||
* @returns Empty string (no fallback icons)
|
||||
*/
|
||||
export function getFallbackIcon(entityType: 'noun' | 'verb' = 'noun'): string {
|
||||
return '' // Clean, minimal display
|
||||
}
|
||||
541
src/augmentations/display/intelligentComputation.ts
Normal file
541
src/augmentations/display/intelligentComputation.ts
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Intelligent Computation Engine
|
||||
*
|
||||
* Leverages existing Brainy AI infrastructure for intelligent field computation:
|
||||
* - IntelligentTypeMatcher for semantic type detection
|
||||
* - Neural Import patterns for field analysis
|
||||
* - JSON processing utilities for field extraction
|
||||
* - Existing NounType/VerbType taxonomy (31+40 types)
|
||||
*/
|
||||
|
||||
import type {
|
||||
ComputedDisplayFields,
|
||||
FieldComputationContext,
|
||||
TypeMatchResult,
|
||||
DisplayConfig
|
||||
} from './types.js'
|
||||
import type { VectorDocument, GraphVerb } from '../../coreTypes.js'
|
||||
import { IntelligentTypeMatcher, getTypeMatcher } from '../typeMatching/intelligentTypeMatcher.js'
|
||||
import { getNounIcon, getVerbIcon } from './iconMappings.js'
|
||||
import {
|
||||
getFieldPatterns,
|
||||
getPriorityFields,
|
||||
extractFieldValue,
|
||||
calculateFieldConfidence
|
||||
} from './fieldPatterns.js'
|
||||
import { prepareJsonForVectorization, extractFieldFromJson } from '../../utils/jsonProcessing.js'
|
||||
import { NounType, VerbType } from '../../types/graphTypes.js'
|
||||
|
||||
/**
|
||||
* Intelligent field computation engine
|
||||
* Coordinates AI-powered analysis with fallback heuristics
|
||||
*/
|
||||
export class IntelligentComputationEngine {
|
||||
private typeMatcher: IntelligentTypeMatcher | null = null
|
||||
private config: DisplayConfig
|
||||
private initialized = false
|
||||
|
||||
constructor(config: DisplayConfig) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the computation engine with AI components
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return
|
||||
|
||||
try {
|
||||
// 🧠 LEVERAGE YOUR EXISTING AI INFRASTRUCTURE
|
||||
this.typeMatcher = await getTypeMatcher()
|
||||
if (this.typeMatcher) {
|
||||
console.log('🎨 Display computation engine initialized with AI intelligence')
|
||||
} else {
|
||||
console.warn('🎨 Display computation engine running in basic mode (AI unavailable)')
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('🎨 AI initialization failed, using heuristic fallback:', error)
|
||||
}
|
||||
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute display fields for a noun using AI-first approach
|
||||
* @param data The noun data/metadata
|
||||
* @param id Optional noun ID
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
async computeNounDisplay(data: any, id?: string): Promise<ComputedDisplayFields> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// 🟢 PRIMARY PATH: Use your existing AI intelligence
|
||||
if (this.typeMatcher) {
|
||||
return await this.computeWithAI(data, 'noun', { id })
|
||||
}
|
||||
|
||||
// 🟡 FALLBACK PATH: Use heuristic patterns
|
||||
return await this.computeWithHeuristics(data, 'noun', { id })
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Display computation failed, using minimal fallback:', error)
|
||||
return this.createMinimalDisplay(data, 'noun')
|
||||
} finally {
|
||||
const computationTime = Date.now() - startTime
|
||||
if (this.config.debugMode) {
|
||||
console.log(`Display computation took ${computationTime}ms`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute display fields for a verb using AI-first approach
|
||||
* @param verb The verb/relationship data
|
||||
* @returns Computed display fields
|
||||
*/
|
||||
async computeVerbDisplay(verb: GraphVerb): Promise<ComputedDisplayFields> {
|
||||
const startTime = Date.now()
|
||||
|
||||
try {
|
||||
// 🟢 PRIMARY PATH: Use your existing AI for verb analysis
|
||||
if (this.typeMatcher) {
|
||||
return await this.computeVerbWithAI(verb)
|
||||
}
|
||||
|
||||
// 🟡 FALLBACK PATH: Use heuristic patterns for verbs
|
||||
return await this.computeVerbWithHeuristics(verb)
|
||||
|
||||
} catch (error) {
|
||||
console.warn('Verb display computation failed, using minimal fallback:', error)
|
||||
return this.createMinimalDisplay(verb, 'verb')
|
||||
} finally {
|
||||
const computationTime = Date.now() - startTime
|
||||
if (this.config.debugMode) {
|
||||
console.log(`Verb display computation took ${computationTime}ms`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-powered computation using your existing IntelligentTypeMatcher
|
||||
* @param data Entity data/metadata
|
||||
* @param entityType Type of entity (noun/verb)
|
||||
* @param options Additional options
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
private async computeWithAI(
|
||||
data: any,
|
||||
entityType: 'noun' | 'verb',
|
||||
options: { id?: string } = {}
|
||||
): Promise<ComputedDisplayFields> {
|
||||
|
||||
// 🧠 USE YOUR EXISTING TYPE DETECTION AI
|
||||
const typeResult = await this.typeMatcher!.matchNounType(data)
|
||||
|
||||
// Create computation context
|
||||
const context: FieldComputationContext = {
|
||||
data,
|
||||
metadata: data,
|
||||
typeResult,
|
||||
config: this.config,
|
||||
entityType
|
||||
}
|
||||
|
||||
// 🟢 INTELLIGENT FIELD EXTRACTION using your patterns + AI insights
|
||||
const displayFields = {
|
||||
title: await this.computeIntelligentTitle(context),
|
||||
description: await this.computeIntelligentDescription(context),
|
||||
type: typeResult.type,
|
||||
tags: await this.computeIntelligentTags(context),
|
||||
confidence: typeResult.confidence,
|
||||
reasoning: this.config.debugMode ? typeResult.reasoning : undefined,
|
||||
alternatives: this.config.debugMode ? typeResult.alternatives : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
|
||||
return displayFields
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-powered verb computation using relationship analysis
|
||||
* @param verb The verb/relationship
|
||||
* @returns AI-computed display fields
|
||||
*/
|
||||
private async computeVerbWithAI(verb: GraphVerb): Promise<ComputedDisplayFields> {
|
||||
|
||||
// 🧠 USE YOUR EXISTING VERB TYPE DETECTION
|
||||
const typeResult = await this.typeMatcher!.matchVerbType(verb)
|
||||
|
||||
// Create verb computation context
|
||||
const context: FieldComputationContext = {
|
||||
data: verb,
|
||||
metadata: verb.metadata || {},
|
||||
typeResult,
|
||||
config: this.config,
|
||||
entityType: 'verb',
|
||||
verbContext: {
|
||||
sourceId: verb.sourceId,
|
||||
targetId: verb.targetId,
|
||||
verbType: verb.type
|
||||
}
|
||||
}
|
||||
|
||||
// 🟢 INTELLIGENT VERB DISPLAY COMPUTATION
|
||||
const displayFields = {
|
||||
title: await this.computeVerbTitle(context),
|
||||
description: await this.computeVerbDescription(context),
|
||||
type: typeResult.type,
|
||||
tags: await this.computeVerbTags(context),
|
||||
relationship: await this.computeHumanReadableRelationship(context),
|
||||
confidence: typeResult.confidence,
|
||||
reasoning: this.config.debugMode ? typeResult.reasoning : undefined,
|
||||
alternatives: this.config.debugMode ? typeResult.alternatives : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
|
||||
return displayFields
|
||||
}
|
||||
|
||||
/**
|
||||
* Heuristic computation when AI is unavailable
|
||||
* @param data Entity data
|
||||
* @param entityType Type of entity
|
||||
* @param options Additional options
|
||||
* @returns Heuristically computed display fields
|
||||
*/
|
||||
private async computeWithHeuristics(
|
||||
data: any,
|
||||
entityType: 'noun' | 'verb',
|
||||
options: { id?: string } = {}
|
||||
): Promise<ComputedDisplayFields> {
|
||||
|
||||
// Use basic type detection
|
||||
const detectedType = this.detectTypeHeuristically(data, entityType)
|
||||
const mockTypeResult: TypeMatchResult = {
|
||||
type: detectedType,
|
||||
confidence: 0.6, // Lower confidence for heuristics
|
||||
reasoning: 'Heuristic detection (AI unavailable)',
|
||||
alternatives: []
|
||||
}
|
||||
|
||||
const context: FieldComputationContext = {
|
||||
data,
|
||||
metadata: data,
|
||||
typeResult: mockTypeResult,
|
||||
config: this.config,
|
||||
entityType
|
||||
}
|
||||
|
||||
// Use pattern-based field extraction
|
||||
const patterns = getFieldPatterns(entityType, detectedType)
|
||||
|
||||
return {
|
||||
title: this.extractFieldWithPatterns(data, patterns, 'title') || 'Untitled',
|
||||
description: this.extractFieldWithPatterns(data, patterns, 'description') || 'No description',
|
||||
type: detectedType,
|
||||
tags: this.extractFieldWithPatterns(data, patterns, 'tags') || [],
|
||||
confidence: mockTypeResult.confidence,
|
||||
reasoning: this.config.debugMode ? mockTypeResult.reasoning : undefined,
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute intelligent title using AI insights and your field extraction
|
||||
* @param context Computation context with AI results
|
||||
* @returns Computed title
|
||||
*/
|
||||
private async computeIntelligentTitle(context: FieldComputationContext): Promise<string> {
|
||||
const { data, typeResult } = context
|
||||
|
||||
// 🟢 USE TYPE-SPECIFIC LOGIC based on your NounType taxonomy
|
||||
switch (typeResult?.type) {
|
||||
case NounType.Person:
|
||||
return this.computePersonTitle(data)
|
||||
|
||||
case NounType.Organization:
|
||||
return this.computeOrganizationTitle(data)
|
||||
|
||||
case NounType.Project:
|
||||
return this.computeProjectTitle(data)
|
||||
|
||||
case NounType.Document:
|
||||
return this.computeDocumentTitle(data)
|
||||
|
||||
default:
|
||||
// 🟢 LEVERAGE YOUR JSON PROCESSING for unknown types
|
||||
return this.extractBestTitle(data, typeResult?.type)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute intelligent description using AI insights and context
|
||||
* @param context Computation context
|
||||
* @returns Enhanced description
|
||||
*/
|
||||
private async computeIntelligentDescription(context: FieldComputationContext): Promise<string> {
|
||||
const { data, typeResult } = context
|
||||
|
||||
// 🟢 USE YOUR EXISTING JSON PROCESSING for vectorization-quality text
|
||||
const priorityFields = getPriorityFields('noun', typeResult?.type)
|
||||
const enhancedText = prepareJsonForVectorization(data, {
|
||||
priorityFields,
|
||||
includeFieldNames: false,
|
||||
maxDepth: 2
|
||||
})
|
||||
|
||||
// Create context-aware description based on type
|
||||
return this.createContextAwareDescription(data, typeResult, enhancedText)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute intelligent tags using type analysis
|
||||
* @param context Computation context
|
||||
* @returns Generated tags array
|
||||
*/
|
||||
private async computeIntelligentTags(context: FieldComputationContext): Promise<string[]> {
|
||||
const { data, typeResult } = context
|
||||
const tags: string[] = []
|
||||
|
||||
// Add type-based tag
|
||||
if (typeResult?.type) {
|
||||
tags.push(typeResult.type.toLowerCase())
|
||||
}
|
||||
|
||||
// Extract explicit tags from data
|
||||
const explicitTags = this.extractExplicitTags(data)
|
||||
tags.push(...explicitTags)
|
||||
|
||||
// Add semantic tags based on AI analysis
|
||||
if (typeResult && this.typeMatcher) {
|
||||
const semanticTags = this.generateSemanticTags(data, typeResult)
|
||||
tags.push(...semanticTags)
|
||||
}
|
||||
|
||||
// Remove duplicates and return
|
||||
return [...new Set(tags.filter(Boolean))]
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute verb title (relationship summary)
|
||||
* @param context Verb computation context
|
||||
* @returns Verb title
|
||||
*/
|
||||
private async computeVerbTitle(context: FieldComputationContext): Promise<string> {
|
||||
const { verbContext, typeResult } = context
|
||||
|
||||
if (!verbContext) return 'Relationship'
|
||||
|
||||
const { sourceId, targetId } = verbContext
|
||||
const relationshipType = typeResult?.type || 'RelatedTo'
|
||||
|
||||
// Try to get readable names for source and target
|
||||
// This could be enhanced to actually resolve the entities
|
||||
return `${sourceId} ${this.getReadableVerbPhrase(relationshipType)} ${targetId}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Create minimal display for error cases
|
||||
* @param data Entity data
|
||||
* @param entityType Entity type
|
||||
* @returns Minimal display fields
|
||||
*/
|
||||
private createMinimalDisplay(data: any, entityType: 'noun' | 'verb'): ComputedDisplayFields {
|
||||
return {
|
||||
title: data.name || data.title || data.id || 'Untitled',
|
||||
description: data.description || data.summary || 'No description available',
|
||||
type: entityType === 'noun' ? 'Item' : 'RelatedTo',
|
||||
tags: [],
|
||||
confidence: 0.1, // Very low confidence for fallback
|
||||
computedAt: Date.now(),
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods for specific noun types
|
||||
private computePersonTitle(data: any): string {
|
||||
if (data.firstName && data.lastName) {
|
||||
return `${data.firstName} ${data.lastName}`.trim()
|
||||
}
|
||||
return data.name || data.fullName || data.displayName || data.firstName || data.lastName || 'Person'
|
||||
}
|
||||
|
||||
private computeOrganizationTitle(data: any): string {
|
||||
return data.name || data.companyName || data.organizationName || data.title || 'Organization'
|
||||
}
|
||||
|
||||
private computeProjectTitle(data: any): string {
|
||||
return data.name || data.projectName || data.title || data.projectTitle || 'Project'
|
||||
}
|
||||
|
||||
private computeDocumentTitle(data: any): string {
|
||||
return data.title || data.filename || data.name || data.subject || 'Document'
|
||||
}
|
||||
|
||||
private extractBestTitle(data: any, type?: string): string {
|
||||
const titleFields = ['name', 'title', 'displayName', 'label', 'subject', 'heading']
|
||||
|
||||
for (const field of titleFields) {
|
||||
if (data[field]) return String(data[field])
|
||||
}
|
||||
|
||||
return data.id || Object.keys(data)[0] || 'Untitled'
|
||||
}
|
||||
|
||||
private createContextAwareDescription(data: any, typeResult?: TypeMatchResult, enhancedText?: string): string {
|
||||
// Start with basic description fields
|
||||
const basicDesc = data.description || data.summary || data.about || data.details
|
||||
|
||||
if (basicDesc) return String(basicDesc)
|
||||
|
||||
// Use enhanced text from JSON processing
|
||||
if (enhancedText && enhancedText.length > 10) {
|
||||
return enhancedText.substring(0, 200) + (enhancedText.length > 200 ? '...' : '')
|
||||
}
|
||||
|
||||
// Generate from available fields
|
||||
const parts = []
|
||||
if (data.role) parts.push(data.role)
|
||||
if (data.company) parts.push(`at ${data.company}`)
|
||||
if (data.location) parts.push(`in ${data.location}`)
|
||||
|
||||
return parts.length > 0 ? parts.join(' ') : 'No description available'
|
||||
}
|
||||
|
||||
private extractExplicitTags(data: any): string[] {
|
||||
const tagFields = ['tags', 'keywords', 'labels', 'categories', 'topics']
|
||||
|
||||
for (const field of tagFields) {
|
||||
if (data[field]) {
|
||||
if (Array.isArray(data[field])) {
|
||||
return data[field].map(String).filter(Boolean)
|
||||
}
|
||||
if (typeof data[field] === 'string') {
|
||||
return data[field].split(/[,;]\s*|\s+/).filter(Boolean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
private generateSemanticTags(data: any, typeResult: TypeMatchResult): string[] {
|
||||
const tags: string[] = []
|
||||
|
||||
// Add confidence-based tags
|
||||
if (typeResult.confidence > 0.9) tags.push('verified')
|
||||
else if (typeResult.confidence < 0.7) tags.push('uncertain')
|
||||
|
||||
// Add type-specific semantic tags
|
||||
if (data.status) tags.push(String(data.status).toLowerCase())
|
||||
if (data.priority) tags.push(String(data.priority).toLowerCase())
|
||||
if (data.category) tags.push(String(data.category).toLowerCase())
|
||||
|
||||
return tags
|
||||
}
|
||||
|
||||
private getReadableVerbPhrase(verbType: string): string {
|
||||
const verbPhrases: Record<string, string> = {
|
||||
[VerbType.WorksWith]: 'works with',
|
||||
[VerbType.MemberOf]: 'is member of',
|
||||
[VerbType.ReportsTo]: 'reports to',
|
||||
[VerbType.CreatedBy]: 'created by',
|
||||
[VerbType.Owns]: 'owns',
|
||||
[VerbType.LocatedAt]: 'located at',
|
||||
[VerbType.Likes]: 'likes',
|
||||
[VerbType.Follows]: 'follows',
|
||||
[VerbType.Supervises]: 'supervises'
|
||||
}
|
||||
|
||||
return verbPhrases[verbType] || 'related to'
|
||||
}
|
||||
|
||||
private async computeVerbDescription(context: FieldComputationContext): Promise<string> {
|
||||
const { data, verbContext, typeResult } = context
|
||||
|
||||
if (data.description) return String(data.description)
|
||||
|
||||
// Generate contextual description for relationship
|
||||
if (verbContext && typeResult) {
|
||||
const parts = []
|
||||
const relationshipPhrase = this.getReadableVerbPhrase(typeResult.type)
|
||||
|
||||
if (data.role) parts.push(`Role: ${data.role}`)
|
||||
if (data.startDate) parts.push(`Since: ${new Date(data.startDate).toLocaleDateString()}`)
|
||||
if (data.department) parts.push(`Department: ${data.department}`)
|
||||
|
||||
return parts.length > 0
|
||||
? `${relationshipPhrase} - ${parts.join(', ')}`
|
||||
: `${relationshipPhrase} relationship`
|
||||
}
|
||||
|
||||
return 'Relationship'
|
||||
}
|
||||
|
||||
private async computeVerbTags(context: FieldComputationContext): Promise<string[]> {
|
||||
const { data, typeResult } = context
|
||||
const tags = ['relationship']
|
||||
|
||||
if (typeResult?.type) {
|
||||
tags.push(typeResult.type.toLowerCase())
|
||||
}
|
||||
|
||||
// Add relationship-specific tags
|
||||
if (data.status) tags.push(String(data.status).toLowerCase())
|
||||
if (data.type) tags.push(String(data.type).toLowerCase())
|
||||
|
||||
return [...new Set(tags)]
|
||||
}
|
||||
|
||||
private async computeHumanReadableRelationship(context: FieldComputationContext): Promise<string> {
|
||||
const { verbContext, typeResult } = context
|
||||
|
||||
if (!verbContext || !typeResult) return 'Related'
|
||||
|
||||
const { sourceId, targetId } = verbContext
|
||||
const phrase = this.getReadableVerbPhrase(typeResult.type)
|
||||
|
||||
return `${sourceId} ${phrase} ${targetId}`
|
||||
}
|
||||
|
||||
private detectTypeHeuristically(data: any, entityType: 'noun' | 'verb'): string {
|
||||
if (entityType === 'verb') return VerbType.RelatedTo
|
||||
|
||||
// Basic heuristics for noun types
|
||||
if (data.firstName || data.lastName || data.email) return NounType.Person
|
||||
if (data.companyName || data.organization) return NounType.Organization
|
||||
if (data.filename || data.fileType) return NounType.Document
|
||||
if (data.projectName || data.initiative) return NounType.Project
|
||||
if (data.taskName || data.todo) return NounType.Task
|
||||
if (data.startDate || data.endDate) return NounType.Event
|
||||
|
||||
return 'Item' // Generic fallback
|
||||
}
|
||||
|
||||
private extractFieldWithPatterns(data: any, patterns: any[], fieldType: string): any {
|
||||
const relevantPatterns = patterns.filter(p => p.displayField === fieldType)
|
||||
|
||||
for (const pattern of relevantPatterns) {
|
||||
for (const field of pattern.fields) {
|
||||
if (data[field]) {
|
||||
return pattern.transform ? pattern.transform(data[field], { data, config: this.config } as any) : data[field]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the computation engine
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
// Cleanup if needed
|
||||
this.typeMatcher = null
|
||||
this.initialized = false
|
||||
}
|
||||
}
|
||||
253
src/augmentations/display/types.ts
Normal file
253
src/augmentations/display/types.ts
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/**
|
||||
* Universal Display Augmentation - Type Definitions
|
||||
*
|
||||
* Clean TypeScript interfaces for the display augmentation system
|
||||
*/
|
||||
|
||||
import type { VectorDocument, GraphVerb } from '../../coreTypes.js'
|
||||
|
||||
/**
|
||||
* Configuration interface for the Universal Display Augmentation
|
||||
*/
|
||||
export interface DisplayConfig {
|
||||
/** Enable/disable the augmentation */
|
||||
enabled: boolean
|
||||
|
||||
/** LRU cache size for computed display fields */
|
||||
cacheSize: number
|
||||
|
||||
/** Use lazy computation (recommended for performance) */
|
||||
lazyComputation: boolean
|
||||
|
||||
/** Batch processing size for multiple requests */
|
||||
batchSize: number
|
||||
|
||||
/** Minimum confidence threshold for AI type detection */
|
||||
confidenceThreshold: number
|
||||
|
||||
// No icon configuration needed - clean, minimal approach
|
||||
|
||||
/** Custom field mappings (userField -> displayField) */
|
||||
customFieldMappings: Record<string, string>
|
||||
|
||||
/** Type-specific priority fields for intelligent detection */
|
||||
priorityFields: Record<string, string[]>
|
||||
|
||||
/** Enable debug mode with reasoning output */
|
||||
debugMode: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed display fields for any noun or verb
|
||||
*/
|
||||
export interface ComputedDisplayFields {
|
||||
/** Primary display name (AI-detected best field combination) */
|
||||
title: string
|
||||
|
||||
/** Enhanced description with context awareness */
|
||||
description: string
|
||||
|
||||
/** Human-readable type name */
|
||||
type: string
|
||||
|
||||
// No icon field - clean, minimal approach
|
||||
|
||||
/** Generated display tags for categorization */
|
||||
tags: string[]
|
||||
|
||||
/** For verbs: human-readable relationship description */
|
||||
relationship?: string
|
||||
|
||||
/** AI confidence score (0-1) */
|
||||
confidence: number
|
||||
|
||||
/** Explanation of type detection reasoning (debug mode) */
|
||||
reasoning?: string
|
||||
|
||||
/** Alternative type suggestions with confidence scores */
|
||||
alternatives?: Array<{
|
||||
type: string
|
||||
confidence: number
|
||||
}>
|
||||
|
||||
/** Timestamp when fields were computed */
|
||||
computedAt: number
|
||||
|
||||
/** Version of augmentation that computed these fields */
|
||||
version: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache entry for computed display fields
|
||||
*/
|
||||
export interface DisplayCacheEntry {
|
||||
fields: ComputedDisplayFields
|
||||
lastAccessed: number
|
||||
accessCount: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Field computation context passed to computation functions
|
||||
*/
|
||||
export interface FieldComputationContext {
|
||||
/** The original data object */
|
||||
data: any
|
||||
|
||||
/** Metadata associated with the object */
|
||||
metadata: any
|
||||
|
||||
/** Type detection result from AI */
|
||||
typeResult?: TypeMatchResult
|
||||
|
||||
/** Display configuration */
|
||||
config: DisplayConfig
|
||||
|
||||
/** Whether this is a noun or verb */
|
||||
entityType: 'noun' | 'verb'
|
||||
|
||||
/** For verbs: source and target information */
|
||||
verbContext?: {
|
||||
sourceId: string
|
||||
targetId: string
|
||||
verbType?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Type matching result from IntelligentTypeMatcher
|
||||
*/
|
||||
export interface TypeMatchResult {
|
||||
type: string
|
||||
confidence: number
|
||||
reasoning: string
|
||||
alternatives: Array<{
|
||||
type: string
|
||||
confidence: number
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced VectorDocument with display capabilities
|
||||
*/
|
||||
export interface EnhancedVectorDocument<T = any> extends VectorDocument<T> {
|
||||
/**
|
||||
* Get computed display field(s)
|
||||
* @param field Optional specific field name
|
||||
* @returns Single field value or all display fields
|
||||
*/
|
||||
getDisplay(): Promise<ComputedDisplayFields>
|
||||
getDisplay(field: keyof ComputedDisplayFields): Promise<any>
|
||||
|
||||
/**
|
||||
* Get available fields for a specific augmentation namespace
|
||||
* @param namespace The augmentation namespace (e.g., 'display')
|
||||
* @returns Array of available field names
|
||||
*/
|
||||
getAvailableFields(namespace: string): string[]
|
||||
|
||||
/**
|
||||
* Get available augmentation namespaces
|
||||
* @returns Array of available augmentation names
|
||||
*/
|
||||
getAvailableAugmentations(): string[]
|
||||
|
||||
/**
|
||||
* Debug exploration of all computed fields
|
||||
*/
|
||||
explore(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced GraphVerb with display capabilities
|
||||
*/
|
||||
export interface EnhancedGraphVerb extends GraphVerb {
|
||||
/**
|
||||
* Get computed display field(s) for relationships
|
||||
* @param field Optional specific field name
|
||||
* @returns Single field value or all display fields
|
||||
*/
|
||||
getDisplay(): Promise<ComputedDisplayFields>
|
||||
getDisplay(field: keyof ComputedDisplayFields): Promise<any>
|
||||
|
||||
/**
|
||||
* Get available fields for a specific augmentation namespace
|
||||
* @param namespace The augmentation namespace (e.g., 'display')
|
||||
* @returns Array of available field names
|
||||
*/
|
||||
getAvailableFields(namespace: string): string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch computation request for performance optimization
|
||||
*/
|
||||
export interface BatchComputationRequest {
|
||||
id: string
|
||||
data: any
|
||||
metadata: any
|
||||
entityType: 'noun' | 'verb'
|
||||
verbContext?: {
|
||||
sourceId: string
|
||||
targetId: string
|
||||
verbType?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch computation result
|
||||
*/
|
||||
export interface BatchComputationResult {
|
||||
id: string
|
||||
fields: ComputedDisplayFields
|
||||
error?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Field pattern for intelligent field detection
|
||||
*/
|
||||
export interface FieldPattern {
|
||||
/** Field names that match this pattern */
|
||||
fields: string[]
|
||||
|
||||
/** Target display field name */
|
||||
displayField: keyof ComputedDisplayFields
|
||||
|
||||
/** Confidence score for this pattern match */
|
||||
confidence: number
|
||||
|
||||
/** Optional: specific noun/verb types this applies to */
|
||||
applicableTypes?: string[]
|
||||
|
||||
/** Optional: transformation function for the field value */
|
||||
transform?: (value: any, context: FieldComputationContext) => string
|
||||
}
|
||||
|
||||
/**
|
||||
* Statistics for the display augmentation
|
||||
*/
|
||||
export interface DisplayAugmentationStats {
|
||||
/** Total number of computations performed */
|
||||
totalComputations: number
|
||||
|
||||
/** Cache hit ratio */
|
||||
cacheHitRatio: number
|
||||
|
||||
/** Average computation time in milliseconds */
|
||||
averageComputationTime: number
|
||||
|
||||
/** Type detection accuracy (when ground truth available) */
|
||||
typeDetectionAccuracy?: number
|
||||
|
||||
/** Most commonly detected types */
|
||||
commonTypes: Array<{
|
||||
type: string
|
||||
count: number
|
||||
percentage: number
|
||||
}>
|
||||
|
||||
/** Performance metrics */
|
||||
performance: {
|
||||
fastestComputation: number
|
||||
slowestComputation: number
|
||||
totalComputationTime: number
|
||||
}
|
||||
}
|
||||
442
src/augmentations/universalDisplayAugmentation.ts
Normal file
442
src/augmentations/universalDisplayAugmentation.ts
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
/**
|
||||
* Universal Display Augmentation
|
||||
*
|
||||
* 🎨 Provides intelligent display fields for any noun or verb using AI-powered analysis
|
||||
*
|
||||
* Features:
|
||||
* - ✅ Leverages existing IntelligentTypeMatcher for semantic type detection
|
||||
* - ✅ Complete icon coverage for all 31 NounTypes + 40+ VerbTypes
|
||||
* - ✅ Zero performance impact with lazy computation and intelligent caching
|
||||
* - ✅ Perfect isolation - can be disabled, replaced, or configured
|
||||
* - ✅ Clean developer experience with zero conflicts
|
||||
* - ✅ TypeScript support with full autocomplete
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
* // User data access (unchanged)
|
||||
* result.firstName // "John"
|
||||
* result.metadata.title // "CEO"
|
||||
*
|
||||
* // Enhanced display (new capabilities)
|
||||
* result.getDisplay('title') // "John Doe" (AI-computed)
|
||||
* result.getDisplay('description') // "CEO at Acme Corp" (enhanced)
|
||||
* result.getDisplay('type') // "Person" (from AI detection)
|
||||
* result.getDisplay() // All display fields
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { BaseAugmentation, AugmentationContext, MetadataAccess } from './brainyAugmentation.js'
|
||||
import type { VectorDocument, GraphVerb } from '../coreTypes.js'
|
||||
import type {
|
||||
DisplayConfig,
|
||||
ComputedDisplayFields,
|
||||
EnhancedVectorDocument,
|
||||
EnhancedGraphVerb,
|
||||
DisplayAugmentationStats
|
||||
} from './display/types.js'
|
||||
import { IntelligentComputationEngine } from './display/intelligentComputation.js'
|
||||
import { DisplayCache, RequestDeduplicator, getGlobalDisplayCache } from './display/cache.js'
|
||||
import { getNounIcon, getVerbIcon, getIconCoverage } from './display/iconMappings.js'
|
||||
|
||||
/**
|
||||
* Universal Display Augmentation
|
||||
*
|
||||
* Self-contained augmentation that provides intelligent display fields
|
||||
* for any data type using existing Brainy AI infrastructure
|
||||
*/
|
||||
export class UniversalDisplayAugmentation extends BaseAugmentation {
|
||||
readonly name = 'display'
|
||||
readonly version = '1.0.0'
|
||||
readonly timing = 'after' as const // Enhance results after main operations
|
||||
readonly priority = 50 // Medium priority - after core operations
|
||||
readonly metadata: MetadataAccess = {
|
||||
reads: '*', // Read all user data for intelligent analysis
|
||||
writes: ['_display'] // Cache computed fields in isolated namespace
|
||||
}
|
||||
operations = ['get', 'search', 'findSimilar', 'getVerb', 'addNoun', 'addVerb'] as const
|
||||
|
||||
// Computed fields declaration for TypeScript support and discovery
|
||||
computedFields = {
|
||||
display: {
|
||||
title: { type: 'string' as const, description: 'Primary display name (AI-computed)' },
|
||||
description: { type: 'string' as const, description: 'Enhanced description with context' },
|
||||
type: { type: 'string' as const, description: 'Human-readable type (from AI detection)' },
|
||||
tags: { type: 'array' as const, description: 'Generated display tags' },
|
||||
relationship: { type: 'string' as const, description: 'Human-readable relationship (verbs only)' },
|
||||
confidence: { type: 'number' as const, description: 'AI confidence score (0-1)' }
|
||||
}
|
||||
}
|
||||
|
||||
// Core components (all self-contained)
|
||||
private computationEngine: IntelligentComputationEngine
|
||||
private displayCache: DisplayCache
|
||||
private requestDeduplicator: RequestDeduplicator
|
||||
private config: DisplayConfig
|
||||
private context: AugmentationContext | null = null
|
||||
|
||||
constructor(config: Partial<DisplayConfig> = {}) {
|
||||
super()
|
||||
|
||||
// Merge with defaults
|
||||
this.config = {
|
||||
enabled: true,
|
||||
cacheSize: 1000,
|
||||
lazyComputation: true,
|
||||
batchSize: 50,
|
||||
confidenceThreshold: 0.7,
|
||||
customIcons: {},
|
||||
customFieldMappings: {},
|
||||
priorityFields: {},
|
||||
debugMode: false,
|
||||
...config
|
||||
}
|
||||
|
||||
// Initialize components
|
||||
this.computationEngine = new IntelligentComputationEngine(this.config)
|
||||
this.displayCache = getGlobalDisplayCache(this.config.cacheSize)
|
||||
this.requestDeduplicator = new RequestDeduplicator(this.config.batchSize)
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the augmentation with AI components
|
||||
* @param context BrainyData context
|
||||
*/
|
||||
async initialize(context: AugmentationContext): Promise<void> {
|
||||
if (!this.config.enabled) {
|
||||
this.log('🎨 Universal Display augmentation disabled')
|
||||
return
|
||||
}
|
||||
|
||||
this.context = context
|
||||
|
||||
try {
|
||||
// Initialize AI-powered computation engine
|
||||
await this.computationEngine.initialize()
|
||||
|
||||
this.log('🎨 Universal Display augmentation initialized successfully')
|
||||
this.log(` Cache size: ${this.config.cacheSize}`)
|
||||
this.log(` Lazy computation: ${this.config.lazyComputation}`)
|
||||
this.log(` Coverage: ${this.getCoverageInfo()}`)
|
||||
|
||||
} catch (error) {
|
||||
this.log('⚠️ Display augmentation initialization warning:', 'warn')
|
||||
this.log(` ${error}`, 'warn')
|
||||
this.log(' Falling back to basic mode', 'warn')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute augmentation - attach display capabilities to results
|
||||
* @param operation The operation being performed
|
||||
* @param params Operation parameters
|
||||
* @param next Function to execute main operation
|
||||
* @returns Enhanced result with display capabilities
|
||||
*/
|
||||
async execute<T = any>(
|
||||
operation: string,
|
||||
params: any,
|
||||
next: () => Promise<T>
|
||||
): Promise<T> {
|
||||
// Always execute main operation first
|
||||
const result = await next()
|
||||
|
||||
// Only enhance if enabled and operation is relevant
|
||||
if (!this.config.enabled || !this.shouldEnhanceOperation(operation)) {
|
||||
return result
|
||||
}
|
||||
|
||||
try {
|
||||
// Enhance result with display capabilities
|
||||
return this.enhanceWithDisplayCapabilities(result, operation) as T
|
||||
} catch (error) {
|
||||
this.log(`Display enhancement failed for ${operation}: ${error}`, 'warn')
|
||||
return result // Return unenhanced result on error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if operation should be enhanced
|
||||
* @param operation Operation name
|
||||
* @returns True if should enhance
|
||||
*/
|
||||
private shouldEnhanceOperation(operation: string): boolean {
|
||||
const enhanceableOps = ['get', 'search', 'findSimilar', 'getVerb']
|
||||
return enhanceableOps.includes(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance result with display capabilities
|
||||
* @param result The operation result
|
||||
* @param operation The operation type
|
||||
* @returns Enhanced result
|
||||
*/
|
||||
private enhanceWithDisplayCapabilities(result: any, operation: string): any {
|
||||
if (!result) return result
|
||||
|
||||
// Handle different result types
|
||||
if (Array.isArray(result)) {
|
||||
// Array of results (search, findSimilar)
|
||||
return result.map(item => this.enhanceEntity(item))
|
||||
} else if (result.id || result.metadata) {
|
||||
// Single entity (get, getVerb)
|
||||
return this.enhanceEntity(result)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhance a single entity with display capabilities
|
||||
* @param entity The entity to enhance
|
||||
* @returns Enhanced entity
|
||||
*/
|
||||
private enhanceEntity(entity: any): EnhancedVectorDocument | EnhancedGraphVerb {
|
||||
if (!entity) return entity
|
||||
|
||||
// Determine if it's a noun or verb
|
||||
const isVerb = this.isVerbEntity(entity)
|
||||
|
||||
// Add display methods
|
||||
const enhanced = {
|
||||
...entity,
|
||||
getDisplay: this.createGetDisplayMethod(entity, isVerb),
|
||||
getAvailableFields: this.createGetAvailableFieldsMethod(),
|
||||
getAvailableAugmentations: this.createGetAvailableAugmentationsMethod(),
|
||||
explore: this.createExploreMethod(entity)
|
||||
}
|
||||
|
||||
return enhanced
|
||||
}
|
||||
|
||||
/**
|
||||
* Create getDisplay method for an entity
|
||||
* @param entity The entity
|
||||
* @param isVerb Whether it's a verb entity
|
||||
* @returns getDisplay function
|
||||
*/
|
||||
private createGetDisplayMethod(entity: any, isVerb: boolean) {
|
||||
return async (field?: keyof ComputedDisplayFields): Promise<any> => {
|
||||
// Generate cache key
|
||||
const cacheKey = this.displayCache.generateKey(
|
||||
entity.id,
|
||||
entity.metadata || entity,
|
||||
isVerb ? 'verb' : 'noun'
|
||||
)
|
||||
|
||||
// Use request deduplicator to prevent duplicate computations
|
||||
const computedFields = await this.requestDeduplicator.deduplicate(
|
||||
cacheKey,
|
||||
async () => {
|
||||
// Check cache first
|
||||
let cached = this.displayCache.get(cacheKey)
|
||||
if (cached) return cached
|
||||
|
||||
// Compute display fields
|
||||
const startTime = Date.now()
|
||||
let computed: ComputedDisplayFields
|
||||
|
||||
if (isVerb) {
|
||||
computed = await this.computationEngine.computeVerbDisplay(entity as GraphVerb)
|
||||
} else {
|
||||
computed = await this.computationEngine.computeNounDisplay(
|
||||
entity.metadata || entity,
|
||||
entity.id
|
||||
)
|
||||
}
|
||||
|
||||
// Cache the result
|
||||
const computationTime = Date.now() - startTime
|
||||
this.displayCache.set(cacheKey, computed, computationTime)
|
||||
|
||||
return computed
|
||||
}
|
||||
)
|
||||
|
||||
// Return specific field or all fields
|
||||
return field ? computedFields[field] : computedFields
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create getAvailableFields method
|
||||
* @returns getAvailableFields function
|
||||
*/
|
||||
private createGetAvailableFieldsMethod() {
|
||||
return (namespace: string): string[] => {
|
||||
if (namespace === 'display') {
|
||||
return ['title', 'description', 'type', 'tags', 'relationship', 'confidence']
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create getAvailableAugmentations method
|
||||
* @returns getAvailableAugmentations function
|
||||
*/
|
||||
private createGetAvailableAugmentationsMethod() {
|
||||
return (): string[] => {
|
||||
return ['display'] // This augmentation provides 'display' namespace
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create explore method for debugging
|
||||
* @param entity The entity
|
||||
* @returns explore function
|
||||
*/
|
||||
private createExploreMethod(entity: any) {
|
||||
return async (): Promise<void> => {
|
||||
console.log(`\n📋 Entity Exploration: ${entity.id || 'unknown'}`)
|
||||
console.log('━'.repeat(50))
|
||||
|
||||
// Show user data
|
||||
console.log('\n👤 User Data:')
|
||||
const userData = entity.metadata || entity
|
||||
for (const [key, value] of Object.entries(userData)) {
|
||||
if (!key.startsWith('_')) {
|
||||
console.log(` • ${key}: ${JSON.stringify(value)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Show computed display fields
|
||||
try {
|
||||
console.log('\n🎨 Display Fields:')
|
||||
const displayMethod = this.createGetDisplayMethod(entity, this.isVerbEntity(entity))
|
||||
const displayFields = await displayMethod()
|
||||
for (const [key, value] of Object.entries(displayFields)) {
|
||||
console.log(` • ${key}: ${JSON.stringify(value)}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(` Error computing display fields: ${error}`)
|
||||
}
|
||||
|
||||
console.log('')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an entity is a verb
|
||||
* @param entity The entity to check
|
||||
* @returns True if it's a verb
|
||||
*/
|
||||
private isVerbEntity(entity: any): boolean {
|
||||
return !!(entity.sourceId && entity.targetId) ||
|
||||
!!(entity.source && entity.target) ||
|
||||
!!entity.verb
|
||||
}
|
||||
|
||||
/**
|
||||
* Get coverage information
|
||||
* @returns Coverage info string
|
||||
*/
|
||||
private getCoverageInfo(): string {
|
||||
return 'Clean display - focuses on AI-powered content'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get augmentation statistics
|
||||
* @returns Performance and usage statistics
|
||||
*/
|
||||
getStats(): DisplayAugmentationStats {
|
||||
return this.displayCache.getStats()
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the augmentation at runtime
|
||||
* @param newConfig Partial configuration to merge
|
||||
*/
|
||||
configure(newConfig: Partial<DisplayConfig>): void {
|
||||
this.config = { ...this.config, ...newConfig }
|
||||
|
||||
if (!this.config.enabled) {
|
||||
this.displayCache.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached display data
|
||||
*/
|
||||
clearCache(): void {
|
||||
this.displayCache.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Precompute display fields for a batch of entities
|
||||
* @param entities Array of entities to precompute
|
||||
*/
|
||||
async precomputeBatch(entities: Array<{ id: string; data: any }>): Promise<void> {
|
||||
const computeRequests = entities.map(({ id, data }) => ({
|
||||
key: this.displayCache.generateKey(id, data, 'noun'),
|
||||
computeFn: () => this.computationEngine.computeNounDisplay(data, id)
|
||||
}))
|
||||
|
||||
await this.displayCache.batchPrecompute(computeRequests)
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional check if this augmentation should run
|
||||
* @param operation Operation name
|
||||
* @param params Operation parameters
|
||||
* @returns True if should execute
|
||||
*/
|
||||
shouldExecute(operation: string, params: any): boolean {
|
||||
return this.config.enabled && this.shouldEnhanceOperation(operation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup when augmentation is shut down
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
try {
|
||||
// Cleanup computation engine
|
||||
await this.computationEngine.shutdown()
|
||||
|
||||
// Cleanup request deduplicator
|
||||
this.requestDeduplicator.shutdown()
|
||||
|
||||
// Clear cache if configured to do so
|
||||
if (this.config.debugMode) {
|
||||
const stats = this.getStats()
|
||||
this.log(`🎨 Display augmentation shutdown statistics:`)
|
||||
this.log(` Total computations: ${stats.totalComputations}`)
|
||||
this.log(` Cache hit ratio: ${(stats.cacheHitRatio * 100).toFixed(1)}%`)
|
||||
this.log(` Average computation time: ${stats.averageComputationTime.toFixed(1)}ms`)
|
||||
}
|
||||
|
||||
this.log('🎨 Universal Display augmentation shut down')
|
||||
|
||||
} catch (error) {
|
||||
this.log(`Display augmentation shutdown error: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function to create display augmentation with default config
|
||||
* @param config Optional configuration overrides
|
||||
* @returns Configured display augmentation instance
|
||||
*/
|
||||
export function createDisplayAugmentation(config: Partial<DisplayConfig> = {}): UniversalDisplayAugmentation {
|
||||
return new UniversalDisplayAugmentation(config)
|
||||
}
|
||||
|
||||
/**
|
||||
* Default configuration for the display augmentation
|
||||
*/
|
||||
export const DEFAULT_DISPLAY_CONFIG: DisplayConfig = {
|
||||
enabled: true,
|
||||
cacheSize: 1000,
|
||||
lazyComputation: true,
|
||||
batchSize: 50,
|
||||
confidenceThreshold: 0.7,
|
||||
customIcons: {},
|
||||
customFieldMappings: {},
|
||||
priorityFields: {},
|
||||
debugMode: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Export for easy import and registration
|
||||
*/
|
||||
export default UniversalDisplayAugmentation
|
||||
Loading…
Add table
Add a link
Reference in a new issue