UNPKG

voice-ai-workforce

Version:

Voice AI assistant for workforce management - hands-free task updates, time tracking, and team communication. Built by VenueBoost Inc.

555 lines (477 loc) 15.6 kB
// packages/core/src/businessContext.ts import { BusinessContext } from '../../types/src/types'; export interface BusinessContextValidation { isValid: boolean; errors: string[]; warnings: string[]; completeness: number; // 0-1 scale } export interface VariableReplacementOptions { preserveUnknownVariables?: boolean; caseSensitive?: boolean; customVariables?: Record<string, string>; enableNestedVariables?: boolean; } export interface ContextMetrics { variablesUsed: string[]; replacementCount: number; unreplacedVariables: string[]; processingTime: number; } /** * Business context manager that handles business-specific branding * and context injection into voice AI responses */ export class BusinessContextManager { private context: BusinessContext; private cachedVariables: Record<string, string> = {}; private variablePattern = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g; constructor(context?: BusinessContext) { this.context = context || this.getDefaultContext(); this.updateVariableCache(); } /** * Get default business context for fallback */ private getDefaultContext(): BusinessContext { return { name: 'your assistant', domain: 'general', capabilities: ['basic commands', 'help', 'information'], website: '', supportEmail: '', brandColor: '#3B82F6', customVariables: {} }; } /** * Update business context */ updateContext(newContext: Partial<BusinessContext>): void { this.context = { ...this.context, ...newContext }; this.updateVariableCache(); } /** * Get current business context */ getContext(): BusinessContext { return { ...this.context }; } /** * Update cached variables for performance */ private updateVariableCache(): void { this.cachedVariables = { // Core business variables businessName: this.context.name, companyName: this.context.name, organizationName: this.context.name, // Business details domain: this.context.domain, industry: this.context.domain, // Capabilities capabilities: this.context.capabilities.join(', '), capabilitiesList: this.context.capabilities.join(', '), primaryCapability: this.context.capabilities[0] || 'general assistance', capabilityCount: this.context.capabilities.length.toString(), // Contact information website: this.context.website || '', supportEmail: this.context.supportEmail || '', // Branding brandColor: this.context.brandColor || '#3B82F6', // Time-based variables currentDate: new Date().toLocaleDateString(), currentTime: new Date().toLocaleTimeString(), currentYear: new Date().getFullYear().toString(), // Formatting variations businessNameUpper: this.context.name.toUpperCase(), businessNameLower: this.context.name.toLowerCase(), businessNameTitle: this.toTitleCase(this.context.name), // Custom variables from context ...this.context.customVariables }; } /** * Replace variables in text with business context values */ replaceVariables( text: string, options: VariableReplacementOptions = {} ): { result: string; metrics: ContextMetrics; } { const startTime = performance.now(); let result = text; const variablesUsed: string[] = []; const unreplacedVariables: string[] = []; let replacementCount = 0; // Merge custom variables with cached variables const allVariables = { ...this.cachedVariables, ...options.customVariables }; // Handle case sensitivity const pattern = options.caseSensitive !== false ? this.variablePattern : new RegExp(this.variablePattern.source, 'gi'); result = result.replace(pattern, (match, variableName) => { const key = options.caseSensitive !== false ? variableName : this.findCaseInsensitiveKey(variableName, allVariables); if (key && allVariables[key] !== undefined) { variablesUsed.push(variableName); replacementCount++; let value = allVariables[key]; // Handle nested variables if enabled if (options.enableNestedVariables && value.includes('{{')) { const nestedResult = this.replaceVariables(value, { ...options, enableNestedVariables: false // Prevent infinite recursion }); value = nestedResult.result; } return value; } else { unreplacedVariables.push(variableName); // Preserve unknown variables if requested return options.preserveUnknownVariables !== false ? match : ''; } }); const metrics: ContextMetrics = { variablesUsed: [...new Set(variablesUsed)], replacementCount, unreplacedVariables: [...new Set(unreplacedVariables)], processingTime: performance.now() - startTime }; return { result, metrics }; } /** * Find case-insensitive key match */ private findCaseInsensitiveKey( targetKey: string, variables: Record<string, string> ): string | undefined { const lowerTarget = targetKey.toLowerCase(); return Object.keys(variables).find(key => key.toLowerCase() === lowerTarget ); } /** * Convert string to title case */ private toTitleCase(str: string): string { return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase() ); } /** * Validate business context completeness and validity */ validateContext(context?: BusinessContext): BusinessContextValidation { const ctx = context || this.context; const errors: string[] = []; const warnings: string[] = []; let completeness = 0; // Required fields validation if (!ctx.name || ctx.name.trim() === '') { errors.push('Business name is required'); } else { completeness += 0.3; } if (!ctx.domain || ctx.domain.trim() === '') { errors.push('Business domain is required'); } else { completeness += 0.2; } if (!ctx.capabilities || ctx.capabilities.length === 0) { errors.push('At least one capability is required'); } else { completeness += 0.2; } // Optional fields that improve completeness if (ctx.website && ctx.website.trim() !== '') { completeness += 0.1; } else { warnings.push('Website not provided - some features may be limited'); } if (ctx.supportEmail && ctx.supportEmail.trim() !== '') { completeness += 0.1; } else { warnings.push('Support email not provided - contact features may be limited'); } if (ctx.brandColor && ctx.brandColor.trim() !== '') { completeness += 0.1; } else { warnings.push('Brand color not provided - using default styling'); } // Validation rules if (ctx.name && ctx.name.length > 100) { warnings.push('Business name is very long - consider shortening for better UX'); } if (ctx.capabilities && ctx.capabilities.length > 10) { warnings.push('Too many capabilities listed - consider grouping for better clarity'); } if (ctx.website && !this.isValidUrl(ctx.website)) { warnings.push('Website URL appears to be invalid'); } if (ctx.supportEmail && !this.isValidEmail(ctx.supportEmail)) { warnings.push('Support email appears to be invalid'); } if (ctx.brandColor && !this.isValidColor(ctx.brandColor)) { warnings.push('Brand color appears to be invalid - using default'); } return { isValid: errors.length === 0, errors, warnings, completeness: Math.min(completeness, 1.0) }; } /** * Simple URL validation */ private isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } /** * Simple email validation */ private isValidEmail(email: string): boolean { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * Simple color validation (hex, rgb, named colors) */ private isValidColor(color: string): boolean { // Hex colors if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color)) { return true; } // RGB/RGBA colors if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+(\s*,\s*[\d.]+)?\s*\)$/.test(color)) { return true; } // Named colors (basic set) const namedColors = [ 'red', 'blue', 'green', 'yellow', 'orange', 'purple', 'pink', 'brown', 'black', 'white', 'gray', 'grey', 'cyan', 'magenta', 'lime', 'navy' ]; return namedColors.includes(color.toLowerCase()); } /** * Generate business context presets for common industries */ static getPresetContext(industry: string): BusinessContext { const presets: Record<string, BusinessContext> = { construction: { name: 'Construction Manager', domain: 'construction', capabilities: [ 'time tracking', 'project management', 'team coordination', 'safety compliance', 'quality control' ], brandColor: '#F59E0B', customVariables: { industry: 'construction', workType: 'field operations', safetyFocus: 'workplace safety' } }, retail: { name: 'Retail Assistant', domain: 'retail', capabilities: [ 'customer service', 'inventory management', 'sales tracking', 'team coordination' ], brandColor: '#10B981', customVariables: { industry: 'retail', workType: 'customer operations', salesFocus: 'customer satisfaction' } }, healthcare: { name: 'Healthcare Assistant', domain: 'healthcare', capabilities: [ 'appointment scheduling', 'patient communication', 'staff coordination', 'compliance tracking' ], brandColor: '#3B82F6', customVariables: { industry: 'healthcare', workType: 'patient care', complianceFocus: 'patient safety' } }, manufacturing: { name: 'Production Assistant', domain: 'manufacturing', capabilities: [ 'production tracking', 'quality control', 'shift management', 'equipment monitoring' ], brandColor: '#8B5CF6', customVariables: { industry: 'manufacturing', workType: 'production operations', qualityFocus: 'product excellence' } }, hospitality: { name: 'Hospitality Manager', domain: 'hospitality', capabilities: [ 'guest services', 'staff scheduling', 'event coordination', 'facility management' ], brandColor: '#EC4899', customVariables: { industry: 'hospitality', workType: 'guest services', serviceFocus: 'guest satisfaction' } } }; return presets[industry] || { name: 'Business Assistant', domain: 'general', capabilities: ['task management', 'team coordination', 'basic operations'], brandColor: '#6B7280', customVariables: { industry: 'general', workType: 'business operations' } }; } /** * Generate context-aware response templates */ generateResponseTemplates(): Record<string, string> { return { greeting: "Hello! I'm your {{businessName}} assistant. I can help you with {{capabilities}}.", introduction: "I'm your {{businessName}} voice assistant, specialized in {{domain}} operations. I can assist with {{capabilities}}. How can I help you today?", help: "I'm your {{businessName}} voice assistant! I can help you with:\n\n{{capabilitiesList}}\n\nWhat would you like to do?", fallback: "I'll connect you to the {{businessName}} system to handle that request. Please wait a moment...", error: "I'm having trouble with that request in {{businessName}}. {{#supportEmail}}You can contact support at {{supportEmail}} for assistance.{{/supportEmail}}", processing: "Processing your {{businessName}} request...", completed: "Your {{businessName}} request has been completed successfully.", needsPermission: "This action requires {{businessName}} system access. Please ensure you're properly authenticated.", businessHours: "I'm your {{businessName}} assistant, available to help with {{capabilities}} during business hours.", capabilities: "As your {{businessName}} assistant, I can help you with: {{capabilitiesList}}. What would you like to do?" }; } /** * Get available variables for documentation/debugging */ getAvailableVariables(): Record<string, string> { return { ...this.cachedVariables, // Add descriptions for documentation '{{businessName}}': 'Primary business/organization name', '{{capabilities}}': 'Comma-separated list of capabilities', '{{domain}}': 'Business domain/industry', '{{currentDate}}': 'Current date in local format', '{{supportEmail}}': 'Business support email address', '{{website}}': 'Business website URL' }; } /** * Extract variables used in a text template */ extractVariables(text: string): string[] { const matches = text.match(this.variablePattern); if (!matches) return []; return matches.map(match => match.replace(/^\{\{|\}\}$/g, '') ); } /** * Preview variable replacement without modifying state */ previewReplacement( text: string, options: VariableReplacementOptions = {} ): { preview: string; variables: Array<{ name: string; value: string; found: boolean; }>; } { const extractedVars = this.extractVariables(text); const allVariables = { ...this.cachedVariables, ...options.customVariables }; const variables = extractedVars.map(varName => ({ name: varName, value: allVariables[varName] || '', found: varName in allVariables })); const { result } = this.replaceVariables(text, options); return { preview: result, variables }; } /** * Add custom variables to context */ addCustomVariables(variables: Record<string, string>): void { this.context.customVariables = { ...this.context.customVariables, ...variables }; this.updateVariableCache(); } /** * Remove custom variables from context */ removeCustomVariables(variableNames: string[]): void { const newCustomVars = { ...this.context.customVariables }; variableNames.forEach(name => { delete newCustomVars[name]; }); this.context.customVariables = newCustomVars; this.updateVariableCache(); } /** * Export context for serialization */ exportContext(): BusinessContext { return JSON.parse(JSON.stringify(this.context)); } /** * Import context from serialized data */ importContext(context: BusinessContext): BusinessContextValidation { const validation = this.validateContext(context); if (validation.isValid) { this.context = context; this.updateVariableCache(); } return validation; } }