UNPKG

toto-agent

Version:

Chatbot agent and reusable components for Toto platform

1,095 lines (1,062 loc) 189 kB
'use strict'; var generativeAi = require('@google/generative-ai'); var firestore = require('firebase/firestore'); async function getGeminiResponse(userMessage, context, apiKey) { try { // Use provided API key or environment variable const key = apiKey || process.env.GOOGLE_AI_API_KEY; if (!key) { throw new Error('API key not provided. Please pass API key as parameter or set GOOGLE_AI_API_KEY environment variable.'); } // Initialize Google AI Studio client with the API key const genAI = new generativeAi.GoogleGenerativeAI(key); // Use Gemini 1.5 Flash model (available via Google AI Studio) const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); // Enhance the prompt with context if needed const prompt = context ? `${JSON.stringify(context)}\n\n${userMessage}` : userMessage; const result = await model.generateContent(prompt); const response = await result.response; const text = response.text(); return text || 'Sorry, I could not generate a response.'; } catch (error) { console.error('Google AI Studio Gemini error:', error); return 'Sorry, I could not generate a response due to API access restrictions.'; } } // Agent Training Configuration System // This module provides dynamic configuration for training agents about the Toto platform class AgentTrainingConfig { constructor() { this.initializeConfigurations(); } static getInstance() { if (!AgentTrainingConfig.instance) { AgentTrainingConfig.instance = new AgentTrainingConfig(); } return AgentTrainingConfig.instance; } initializeConfigurations() { // Initialize platform configuration this.platformConfig = { name: "Toto", version: "1.0", description: "A pet rescue platform featuring an interactive feed, secure donations, guardian case management, and social sharing", mission: "Connecting users with pets in need through an engaging platform that enables direct support and transparent rescue operations", coreValues: [ "Animal welfare first", "Transparent operations", "Community-driven support", "Secure and trustworthy transactions", "Inclusive and accessible platform" ], features: [ "Interactive pet rescue case feed", "Secure donation system with transparent tracking", "Guardian dashboard for case management", "Social sharing integration", "Real-time updates and notifications", "Role-based access (users, guardians, admins)", "Multi-language support", "Case progress tracking", "Community engagement tools" ], userRoles: ["user", "guardian", "admin"], supportedRegions: ["Global", "Argentina"], // Added Argentina specifically primaryMarkets: ["Argentina", "Spanish-speaking countries", "English-speaking countries"] }; // Initialize language configurations this.languageConfigs = new Map(); // Spanish configuration (Argentina-focused) this.languageConfigs.set('es', { code: 'es', name: 'Español (Argentina)', isDefault: true, keywords: { help: ['ayudar', 'ayuda', 'necesita', 'necesito', 'apoyo', 'che', 'boludo', 'pibe'], share: ['compartir', 'difundir', 'contar', 'publicar', 'subir'], adopt: ['adoptar', 'adopción', 'rescatar'], volunteer: ['voluntario', 'voluntaria', 'voluntariado', 'colaborar'], thank: ['gracias', 'agradezco', 'agradecido', 'agradecida', 'genial', 'bárbaro'], love: ['amo', 'encanta', 'adoro', 'quiero', 'me copa', 'me gusta'], emergency: ['urgente', 'emergencia', 'crítico', 'inmediato', 'ya', 'rápido'] }, responses: { error: 'Disculpá, encontré un error al procesar tu solicitud. Por favor intentalo de nuevo.', noResponse: 'Disculpá, no pude generar una respuesta.', donation: { complete: 'Completar donación', share: 'Compartir después de donar', follow: 'Seguir actualizaciones del caso' }, sharing: { choosePlatform: 'Elegí la plataforma para compartir', customize: 'Personalizá el mensaje', addStory: 'Agregá tu historia personal', tagFriends: 'Etiquetá a tus amigos que puedan ayudar' }, engagement: { shareWithFriends: 'Compartí con tus amigos', makeDonation: 'Hacé una pequeña donación', followUpdates: 'Seguí para tener actualizaciones', exploreOther: 'Explorá otros casos' } } }); // English configuration this.languageConfigs.set('en', { code: 'en', name: 'English', isDefault: false, keywords: { help: ['help', 'need', 'assist', 'support'], share: ['share', 'spread', 'tell', 'post'], adopt: ['adopt', 'adoption', 'rescue'], volunteer: ['volunteer', 'help out', 'get involved'], thank: ['thank', 'grateful', 'appreciate'], love: ['love', 'heart', 'adore'], emergency: ['urgent', 'emergency', 'critical', 'immediate'] }, responses: { error: 'Sorry, I encountered an error while processing your request. Please try again.', noResponse: 'Sorry, I could not generate a response.', donation: { complete: 'Complete donation', share: 'Share after donating', follow: 'Follow case updates' }, sharing: { choosePlatform: 'Choose platform to share', customize: 'Customize share message', addStory: 'Add personal story', tagFriends: 'Tag friends who might help' }, engagement: { shareWithFriends: 'Share with friends', makeDonation: 'Make a small donation', followUpdates: 'Follow for updates', exploreOther: 'Explore other cases' } } }); // Initialize training dynamics this.trainingDynamics = { userAdaptation: { firstTimeUserGuidance: true, roleBasedResponses: true, engagementLevelAdjustment: true, locationAwareness: true }, contentPersonalization: { animalTypePreferences: true, urgencyDetection: true, donationHistoryConsideration: true, shareHistoryConsideration: true }, safetyChecks: { medicalAdviceDetection: true, promiseGuaranteeDetection: true, privacyRespect: true, transparencyRequirement: true }, responseBehavior: { maxResponseLength: 150, preferredTone: 'warm', emojiUsage: true, actionSuggestionLimit: 3 } }; } // Public getters for configuration access getPlatformConfig() { return { ...this.platformConfig }; } getLanguageConfig(languageCode) { return this.languageConfigs.get(languageCode); } getAllLanguageConfigs() { return Array.from(this.languageConfigs.values()); } getDefaultLanguage() { return Array.from(this.languageConfigs.values()).find(config => config.isDefault) || this.languageConfigs.get('es'); } getTrainingDynamics() { return { ...this.trainingDynamics }; } // Dynamic configuration updates (for admin/system updates) updatePlatformConfig(updates) { this.platformConfig = { ...this.platformConfig, ...updates }; } addLanguageSupport(languageConfig) { this.languageConfigs.set(languageConfig.code, languageConfig); } updateTrainingDynamics(updates) { this.trainingDynamics = { ...this.trainingDynamics, ...updates, userAdaptation: { ...this.trainingDynamics.userAdaptation, ...updates.userAdaptation }, contentPersonalization: { ...this.trainingDynamics.contentPersonalization, ...updates.contentPersonalization }, safetyChecks: { ...this.trainingDynamics.safetyChecks, ...updates.safetyChecks }, responseBehavior: { ...this.trainingDynamics.responseBehavior, ...updates.responseBehavior } }; } // Helper methods for agents detectLanguageFromKeywords(userMessage) { const message = userMessage.toLowerCase(); let bestMatch = this.getDefaultLanguage().code; let maxScore = 0; for (const [langCode, config] of this.languageConfigs) { let score = 0; const allKeywords = Object.values(config.keywords).flat(); for (const keyword of allKeywords) { if (message.includes(keyword.toLowerCase())) { score++; } } if (score > maxScore) { maxScore = score; bestMatch = langCode; } } return bestMatch; } getKeywordsForLanguage(languageCode, category) { const config = this.getLanguageConfig(languageCode); return config ? config.keywords[category] : []; } getResponseTemplate(languageCode, category, subCategory) { var _a; const config = this.getLanguageConfig(languageCode); if (!config) return ''; if (subCategory) { return ((_a = config.responses[category]) === null || _a === void 0 ? void 0 : _a[subCategory]) || ''; } return config.responses[category] || ''; } // Export configuration for external systems exportConfiguration() { return { platform: this.platformConfig, languages: Array.from(this.languageConfigs.values()), dynamics: this.trainingDynamics, exportedAt: new Date().toISOString() }; } } class BaseAgent { constructor(apiKey, agentType) { this.genAI = new generativeAi.GoogleGenerativeAI(apiKey); this.agentType = agentType; this.config = AgentTrainingConfig.getInstance(); } /** * Dynamic global rules based on platform knowledge and user context */ getGlobalRules(userContext = {}) { const platformConfig = this.config.getPlatformConfig(); const trainingDynamics = this.config.getTrainingDynamics(); const language = userContext.language || this.config.getDefaultLanguage().code; const isFirstTime = userContext.isFirstTime || false; // CRITICAL: Spanish language enforcement for Argentina users const isSpanishUser = language === 'es'; const spanishEnforcement = isSpanishUser ? ` 🚨 MANDATORY SPANISH LANGUAGE REQUIREMENTS: - RESPOND ONLY IN SPANISH (ARGENTINIAN) - This is CRITICAL and NON-NEGOTIABLE - NEVER use English words, phrases, or sentences - Use "voseo" argentino when appropriate (vos, querés, podés, etc.) - If you detect yourself starting to respond in English, STOP and restart in Spanish - This is for an ARGENTINIAN user - all communication must be in español argentino - Check every word before responding - NO ENGLISH ALLOWED ` : ''; return ` MANDATORY GLOBAL RULES FOR ALL TOTO AGENTS: ${spanishEnforcement} 🌎 PLATFORM KNOWLEDGE: - Platform: ${platformConfig.name} (v${platformConfig.version}) - ${platformConfig.description} - Mission: ${platformConfig.mission} - Core Values: ${platformConfig.coreValues.join(', ')} - Key Features: ${platformConfig.features.join(', ')} - User Roles: ${platformConfig.userRoles.join(', ')} - Supported Regions: ${platformConfig.supportedRegions.join(', ')} 🗣️ LANGUAGE & COMMUNICATION: - Primary Language: ${language === 'es' ? 'ESPAÑOL ARGENTINO (OBLIGATORIO)' : 'English'} (user preference detected) - CRITICAL: ${language === 'es' ? 'RESPOND ONLY IN SPANISH - This is MANDATORY. Never use English.' : 'Respond in English'} - Tone: ${trainingDynamics.responseBehavior.preferredTone}, conversational, empathetic - Focus: Animal welfare and community building - Approach: Encouraging and positive about rescue efforts ${language === 'es' ? '- LANGUAGE ENFORCEMENT: Use Argentinian Spanish (voseo). Never switch to English under any circumstance.' : ''} 📝 RESPONSE ADAPTATION: - Length: Concise responses (max ${trainingDynamics.responseBehavior.maxResponseLength} words unless specified) - Clarity: Simple language, avoid technical jargon - Structure: Digestible information chunks - Context-Aware: ${isFirstTime && trainingDynamics.userAdaptation.firstTimeUserGuidance ? 'Explain platform basics for new users' : 'Assume platform familiarity'} - Emoji Usage: ${trainingDynamics.responseBehavior.emojiUsage ? 'Appropriate emojis allowed' : 'Text only'} 🔒 SAFETY & ETHICS: - Medical Advice: ${trainingDynamics.safetyChecks.medicalAdviceDetection ? 'NEVER provide - always refer to veterinarians' : 'General guidance only'} - Promises: ${trainingDynamics.safetyChecks.promiseGuaranteeDetection ? 'No guarantees about adoption timelines or outcomes' : 'Be cautious with commitments'} - Privacy: ${trainingDynamics.safetyChecks.privacyRespect ? 'Respect user data, minimal personal info requests' : 'Standard privacy practices'} - Transparency: ${trainingDynamics.safetyChecks.transparencyRequirement ? 'Clear about donation usage and platform policies' : 'Honest communication'} 🎯 CONTEXTUAL ACTION GUIDANCE: - If case context is available: End with case-specific action question using pet name - Spanish with case: "¿Qué querés hacer para ayudar a [PetName]? ¡Tocá los íconos para ayudar!" - English with case: "What do you want to do to help [PetName]? Tap the icons to help!" - Without case context: End with general helpful question - Spanish general: "¿Cómo puedo ayudarte más?" - English general: "How can I help you further?" 🎯 ENGAGEMENT STRATEGY: - Actions: Encourage donate, share, adopt, volunteer based on context - Impact: Connect emotions to concrete help for animals - Community: Build ongoing engagement and connections - Results: Focus on real impact stories and outcomes - Personalization: ${trainingDynamics.contentPersonalization.animalTypePreferences ? 'Adapt to user animal preferences' : 'General animal focus'} ⚠️ CONTENT GUIDELINES: - Accuracy: Honest reporting of animal situations - Urgency: ${trainingDynamics.contentPersonalization.urgencyDetection ? 'Detect and respond appropriately to emergencies' : 'Balanced approach to urgency'} - Helpfulness: Provide actionable information - Boundaries: Professional yet ${trainingDynamics.responseBehavior.preferredTone} interaction 🚫 CRITICAL RESTRICTIONS: - No medical diagnosis or treatment advice - No promises about specific outcomes - No personal information sharing (guardians/adopters) - No manipulation or guilt tactics - No misleading platform information - ${trainingDynamics.userAdaptation.locationAwareness ? 'No assumptions about user location/culture without context' : 'General approach for all users'} ${isSpanishUser ? '- ABSOLUTELY NO ENGLISH WORDS OR PHRASES - Spanish only!' : ''} 🌐 MULTI-LANGUAGE CONSIDERATIONS: - Currently supporting: ${this.config.getAllLanguageConfigs().map(c => c.name).join(', ')} - Cultural Sensitivity: Adapt communication style to user's cultural context - Localization: Use appropriate examples and references for user's region These rules adapt based on user context and platform evolution.`; } /** * Enhanced prompt generation with dynamic rules */ buildPromptWithGlobalRules(specificPrompt, userContext = {}) { return `${this.getGlobalRules(userContext)} SPECIFIC AGENT INSTRUCTIONS: ${specificPrompt} USER CONTEXT: ${this.formatUserContext(userContext)} Execute the specific instructions while following ALL global rules above.`; } /** * Format user context for AI understanding */ formatUserContext(userContext) { const contextParts = []; if (userContext.language) { contextParts.push(`Language: ${userContext.language}`); } if (userContext.location) { contextParts.push(`Location: ${userContext.location}`); } if (userContext.userRole) { contextParts.push(`Role: ${userContext.userRole}`); } if (userContext.isFirstTime) { contextParts.push(`First-time user: Provide platform orientation`); } if (userContext.previousEngagement) { contextParts.push(`Engagement level: ${userContext.previousEngagement}`); } return contextParts.length > 0 ? contextParts.join(', ') : 'No specific context provided'; } /** * Enhanced language detection using configuration */ detectLanguage(userMessage) { // Use the enhanced detection from configuration const detectedLang = this.config.detectLanguageFromKeywords(userMessage); // Fallback to simple detection if needed if (detectedLang === this.config.getDefaultLanguage().code) { const spanishWords = this.config.getKeywordsForLanguage('es', 'help') .concat(this.config.getKeywordsForLanguage('es', 'share')) .concat(['que', 'como', 'donde', 'cuando', 'por', 'para', 'con', 'en', 'el', 'la']); const words = userMessage.toLowerCase().split(' '); const spanishMatches = words.filter(word => spanishWords.includes(word)).length; // Lower threshold: if at least 1 match, treat as Spanish (for short messages) return spanishMatches > 0 ? 'es' : 'en'; } return detectedLang; } /** * Build user context from available information */ buildUserContext(context, userMessage) { var _a; let detectedLanguage = this.config.getDefaultLanguage().code; if (context.language) { detectedLanguage = context.language; } else if ((_a = context.locale) === null || _a === void 0 ? void 0 : _a.startsWith('es')) { detectedLanguage = 'es'; } else if (context.location === 'Argentina') { detectedLanguage = 'es'; } else if (context.languageInstructions && (context.languageInstructions.includes('español') || context.languageInstructions.includes('argentino'))) { detectedLanguage = 'es'; } else if (userMessage) { detectedLanguage = this.detectLanguage(userMessage); } // Always set isFirstTime to false if not present return { ...context, language: detectedLanguage, isFirstTime: context.isFirstTime === undefined ? false : context.isFirstTime }; } /** * Enhanced response generation with dynamic context */ async generateResponse(prompt, context = {}, userMessage) { var _a, _b; try { const model = this.genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); const userContext = this.buildUserContext(context, userMessage); const finalPrompt = this.buildPromptWithGlobalRules(prompt, userContext); // Retry logic for API overload const maxRetries = 3; let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await model.generateContent(finalPrompt); const response = await result.response; const text = response.text() || this.config.getResponseTemplate(userContext.language || 'es', 'noResponse'); // Validate response quality if (this.isResponseValid(text, userContext)) { return text; } else { console.warn(`⚠️ ${this.agentType} generated invalid response, attempting recovery...`); return this.handleInvalidResponse(text, userContext); } } catch (error) { lastError = error; // If it's an overload error and we have retries left, wait and retry if (((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes('503')) || ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes('overloaded'))) { if (attempt < maxRetries) { const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff console.log(`⏳ API overloaded, retrying in ${waitTime}ms (attempt ${attempt}/${maxRetries})`); await new Promise(resolve => setTimeout(resolve, waitTime)); continue; } else { console.error(`❌ API still overloaded after ${maxRetries} retries, falling back to basic response`); return this.generateFallbackResponse(userContext); } } else { // For other errors, don't retry throw error; } } } throw lastError; } catch (error) { console.error(`❌ ${this.agentType} generation error:`, error); return this.handleGenerationError(error, context); } } /** * Generate streaming response for better user experience * Implementation of Google AI Studio's streaming recommendation */ async generateStreamingResponse(prompt, context = {}, userMessage, onChunk) { try { const model = this.genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); const userContext = this.buildUserContext(context, userMessage); const finalPrompt = this.buildPromptWithGlobalRules(prompt, userContext); const result = await model.generateContentStream(finalPrompt); let fullText = ''; for await (const chunk of result.stream) { const chunkText = chunk.text(); fullText += chunkText; // Call the chunk callback if provided if (onChunk && chunkText) { onChunk(chunkText); } } const finalText = fullText || this.config.getResponseTemplate(userContext.language || 'es', 'noResponse'); // Validate response quality const validation = this.validateResponse(finalText, userContext); if (!validation.isValid) { console.warn(`Response validation issues for ${this.agentType}:`, validation.issues); } return finalText; } catch (error) { console.error(`${this.agentType} streaming generation error:`, error); const userContext = this.buildUserContext(context, userMessage); const fallbackText = this.config.getResponseTemplate(userContext.language || 'es', 'error'); // Send fallback as a single chunk if callback provided if (onChunk) { onChunk(fallbackText); } return fallbackText; } } /** * Enhanced response validation with context awareness */ validateResponse(response, userContext) { const issues = []; const trainingDynamics = this.config.getTrainingDynamics(); // Length validation if (response.length > trainingDynamics.responseBehavior.maxResponseLength * 7) { // Allow some buffer issues.push('Response too long - consider breaking into smaller parts'); } // Medical advice detection (multi-language) if (trainingDynamics.safetyChecks.medicalAdviceDetection) { const medicalKeywords = userContext.language === 'es' ? ['diagnóstico', 'medicamento', 'dosis', 'tratamiento médico', 'prescribir'] : ['diagnosis', 'medication', 'dosage', 'medical treatment', 'prescribe']; if (medicalKeywords.some(keyword => response.toLowerCase().includes(keyword))) { issues.push('Potential medical advice detected - redirect to veterinarian'); } } // Promise/guarantee detection (multi-language) if (trainingDynamics.safetyChecks.promiseGuaranteeDetection) { const promiseKeywords = userContext.language === 'es' ? ['garantizo', 'prometo', 'seguramente se curará', 'definitivamente adoptado'] : ['guarantee', 'promise', 'will definitely', 'certainly will be']; if (promiseKeywords.some(keyword => response.toLowerCase().includes(keyword))) { issues.push('Avoid making promises or guarantees about outcomes'); } } return { isValid: issues.length === 0, issues }; } /** * Enhanced context extraction with user awareness */ extractContextSummary(context) { const summary = { petName: context.petName || 'Unknown', status: context.status || 'active', emergency: context.emergency || false, hasCase: !!context.caseId, animalType: context.animalType || 'pet', userRole: context.userRole || 'user', location: context.userLocation || 'Unknown' }; return `Pet: ${summary.petName}, Status: ${summary.status}, Emergency: ${summary.emergency ? 'Yes' : 'No'}, User: ${summary.userRole}`; } /** * Get localized response templates */ getLocalizedResponses(languageCode, category) { const langConfig = this.config.getLanguageConfig(languageCode); if (!langConfig) return null; const responses = langConfig.responses; return responses[category] || null; } /** * Check if feature is enabled in training dynamics */ isFeatureEnabled(feature) { const dynamics = this.config.getTrainingDynamics(); // Navigate nested object structure const parts = feature.split('.'); let current = dynamics; for (const part of parts) { if (current && typeof current === 'object' && part in current) { current = current[part]; } else { return false; } } return Boolean(current); } /** * Check if response is valid */ isResponseValid(text, userContext) { if (!text || text.trim().length === 0) { return false; } // Check for common error patterns const errorPatterns = [ 'error occurred', 'something went wrong', 'unable to process', 'try again later' ]; const lowerText = text.toLowerCase(); return !errorPatterns.some(pattern => lowerText.includes(pattern)); } /** * Handle invalid response by attempting recovery */ handleInvalidResponse(text, userContext) { console.warn(`⚠️ ${this.agentType} received invalid response, using fallback`); return this.generateFallbackResponse(userContext); } /** * Generate fallback response when API is overloaded */ generateFallbackResponse(userContext) { const language = userContext.language || 'es'; const fallbackResponses = { es: '🐾 ¡Hola! El sistema está un poco ocupado ahora mismo, pero estoy aquí para ayudarte con tu mascota. ¿Podrías intentar de nuevo en un momento?', en: '🐾 Hi there! The system is a bit busy right now, but I\'m here to help with your pet. Could you try again in a moment? Touch the paw for more options!' }; return fallbackResponses[language] || fallbackResponses.es; } /** * Handle generation errors gracefully */ handleGenerationError(error, context) { const userContext = this.buildUserContext(context); const language = userContext.language || 'es'; console.error(`❌ ${this.agentType} generation error:`, error.message); const errorResponses = { es: '🐾 Disculpa, tuve un pequeño problema técnico. ¡Pero no te preocupes! Estoy aquí para ayudarte con tu mascota. ¿Podrías intentar de nuevo?', en: '🐾 Sorry, I had a small technical issue. But don\'t worry! I\'m here to help with your pet. Could you try again? Touch the paw for more options!' }; return errorResponses[language] || errorResponses.es; } } exports.UserAction = void 0; (function (UserAction) { UserAction["DONATE"] = "donate"; UserAction["SHARE"] = "share"; UserAction["DIALOG"] = "dialog"; UserAction["LIKE"] = "like"; UserAction["TYPE"] = "type"; UserAction["ADOPT"] = "adopt"; UserAction["VOLUNTEER"] = "volunteer"; UserAction["EMERGENCY"] = "emergency"; UserAction["LOCATION"] = "location"; UserAction["CONTACT"] = "contact"; UserAction["UNKNOWN"] = "unknown"; })(exports.UserAction || (exports.UserAction = {})); exports.AgentType = void 0; (function (AgentType) { AgentType["ORCHESTRATOR"] = "orchestrator"; AgentType["DONATION_AGENT"] = "donation_agent"; AgentType["SHARING_AGENT"] = "sharing_agent"; AgentType["DIALOG_AGENT"] = "dialog_agent"; AgentType["ENGAGEMENT_AGENT"] = "engagement_agent"; AgentType["ADOPTION_AGENT"] = "adoption_agent"; AgentType["ONBOARDING_AGENT"] = "onboarding_agent"; AgentType["SYNTHESIS_AGENT"] = "synthesis_agent"; })(exports.AgentType || (exports.AgentType = {})); class DonationAgent extends BaseAgent { constructor(apiKey) { super(apiKey, exports.AgentType.DONATION_AGENT); } async process(userMessage, context, intent) { // Build user context for dynamic response const userContext = this.buildUserContext(context, userMessage); const { entities } = intent || {}; const isSpanish = userContext.language === 'es'; // Check if user is confirming a donation (not just expressing interest) const isConfirmingDonation = this.isConfirmingDonation(userMessage, isSpanish); const suggestedAmount = (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context); if (isConfirmingDonation) { // User is confirming donation - give confirmation response const confirmationMessage = isSpanish ? `¡Perfecto! Tu donación de $${suggestedAmount} ha sido recibida. ¡Gracias por tu generosidad! Tu contribución ayudará directamente a ${context.petName || 'esta mascota'} a recibir la atención que necesita.` : `Perfect! Your $${suggestedAmount} donation has been received. Thank you for your generosity! Your contribution will directly help ${context.petName || 'this pet'} receive the care they need.`; return { agentType: this.agentType, response: confirmationMessage, actions: [ { type: 'DONATION_CONFIRMED', payload: { amount: suggestedAmount, caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId, petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName, currency: this.getCurrencyForUser(userContext) } } ], nextSteps: this.getLocalizedNextSteps(isSpanish), metadata: { suggestedAmount, urgencyLevel: this.calculateUrgency(context), detectedLanguage: userContext.language, userRole: userContext.userRole, isFirstTimeDonor: userContext.isFirstTime, donationConfirmed: true } }; } // Regular donation interest response const donationPrompt = ` User message: "${userMessage}" Context: ${this.extractContextSummary(context)} Detected entities: ${JSON.stringify(entities)} User role: ${userContext.userRole || 'user'} Your specialized role as donation assistant: - Help users understand the donation process and impact - Guide them through making secure donations - Suggest appropriate amounts based on case needs - Explain how donations directly help specific pets - Be encouraging, grateful, and transparent about fund usage - Build trust through clear communication Language: ${isSpanish ? 'Respond in Spanish with warmth and gratitude' : 'Respond in English with warmth and gratitude'} ${userContext.isFirstTime ? 'NOTE: First-time user - explain donation security and transparency.' : ''} If specific amount mentioned, acknowledge it positively. If specific case/pet mentioned, personalize for that animal's needs. MANDATORY ENDING: ${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?"' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?"'}`; const responseText = await this.generateResponse(donationPrompt, context, userMessage); return { agentType: this.agentType, response: responseText, actions: [ { type: 'SHOW_DONATION_FORM', payload: { suggestedAmount: (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context), caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId, petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName, currency: this.getCurrencyForUser(userContext) } } ], nextSteps: this.getLocalizedNextSteps(isSpanish), metadata: { suggestedAmount: (entities === null || entities === void 0 ? void 0 : entities.amount) || this.calculateSuggestedAmount(context), urgencyLevel: this.calculateUrgency(context), detectedLanguage: userContext.language, userRole: userContext.userRole, isFirstTimeDonor: userContext.isFirstTime } }; } calculateSuggestedAmount(context) { // Smart amount calculation based on case context if (context.emergency) return 50; // Higher for emergencies if (context.fundingGoal && context.raised) { const remaining = context.fundingGoal - context.raised; if (remaining <= 100) return Math.min(remaining, 25); // Help close the gap if (remaining <= 500) return 50; } return 25; // Default amount } getCurrencyForUser(userContext) { // Default to USD, but can be expanded based on user location if (userContext.location) { const countryToCurrency = { 'Mexico': 'MXN', 'Argentina': 'ARS', 'Colombia': 'COP', 'Spain': 'EUR' }; return countryToCurrency[userContext.location] || 'USD'; } return 'USD'; } getLocalizedNextSteps(isSpanish) { return isSpanish ? [ 'Completar donación', 'Compartir después de donar', 'Seguir actualizaciones del caso' ] : [ 'Complete donation', 'Share after donating', 'Follow case updates' ]; } calculateUrgency(context) { // Enhanced urgency calculation with more factors if (context.emergency || context.medicalUrgent) return 'high'; if (context.fundingGoal && context.raised) { const percentage = (context.raised / context.fundingGoal) * 100; if (percentage < 30) return 'high'; if (percentage < 70) return 'medium'; } // Consider time factors if (context.daysActive > 30) return 'medium'; // Older cases need attention if (context.animalAge === 'young' || context.animalAge === 'senior') return 'medium'; return 'low'; } isConfirmingDonation(userMessage, isSpanish) { const message = userMessage.toLowerCase().trim(); // Spanish confirmation patterns if (isSpanish) { const spanishConfirmations = [ 'si, quiero', 'sí, quiero', 'si quiero', 'sí quiero', 'si, quiero.', 'sí, quiero.', 'si quiero.', 'sí quiero.', 'quiero', 'confirmo', 'acepto', 'perfecto', 'ok', 'okay', 'dale', 'vamos', 'hagámoslo', 'hagamoslo', 'procedo', 'continúo', 'adelante', 'proceder', 'continuar', 'hacer la donación', 'donar ahora', 'donar ya', 'proceder con la donación', 'confirmar donación', 'aceptar donación' ]; return spanishConfirmations.some(pattern => message.includes(pattern)); } // English confirmation patterns const englishConfirmations = [ 'yes', 'yes i want', 'yes i do', 'i want to', 'i do', 'confirm', 'accept', 'proceed', 'continue', 'go ahead', 'let\'s do it', 'let\'s go', 'ok', 'okay', 'sure', 'absolutely', 'definitely', 'proceed with donation', 'confirm donation', 'accept donation', 'donate now', 'donate today' ]; return englishConfirmations.some(pattern => message.includes(pattern)); } } class SharingAgent extends BaseAgent { constructor(apiKey) { super(apiKey, exports.AgentType.SHARING_AGENT); } async process(userMessage, context, intent) { // Build user context for dynamic response const userContext = this.buildUserContext(context, userMessage); const { entities } = intent || {}; const isSpanish = userContext.language === 'es'; const sharingPrompt = ` User message: "${userMessage}" Context: ${this.extractContextSummary(context)} Detected entities: ${JSON.stringify(entities)} User role: ${userContext.userRole || 'user'} Your specialized role as sharing assistant: - Help users share pet rescue cases effectively on social media - Generate compelling, shareable content - Recommend optimal platforms for maximum reach - Provide tips for successful awareness campaigns - Create engaging hashtags and social media strategies - Focus on authentic storytelling that drives action Language: ${isSpanish ? 'Respond in Spanish with enthusiasm for spreading awareness' : 'Respond in English with enthusiasm for spreading awareness'} ${userContext.isFirstTime ? 'NOTE: First-time user - explain how sharing helps and platform features.' : ''} If specific platform mentioned, focus strategy on that platform. If specific pet/case mentioned, personalize sharing strategy for that animal. MANDATORY ENDING: ${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?"' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?"'}`; const responseText = await this.generateResponse(sharingPrompt, context, userMessage); const platforms = (entities === null || entities === void 0 ? void 0 : entities.platform) ? [entities.platform] : this.getRecommendedPlatforms(context); return { agentType: this.agentType, response: responseText, actions: [ { type: 'SHOW_SHARE_OPTIONS', payload: { platforms, caseId: (entities === null || entities === void 0 ? void 0 : entities.caseId) || context.caseId, petName: (entities === null || entities === void 0 ? void 0 : entities.petName) || context.petName, shareMessage: this.generateShareMessage(context, isSpanish), hashtags: this.generateHashtags(context, isSpanish), language: userContext.language } } ], nextSteps: this.getLocalizedNextSteps(isSpanish), metadata: { recommendedPlatforms: this.getRecommendedPlatforms(context), viralPotential: this.assessViralPotential(context), detectedLanguage: userContext.language, userRole: userContext.userRole, targetAudience: this.identifyTargetAudience(context, userContext) } }; } generateShareMessage(context, isSpanish) { const petName = context.petName || (isSpanish ? 'esta adorable mascota' : 'this sweet pet'); const urgency = context.emergency ? (isSpanish ? '🚨 URGENTE: ' : '🚨 URGENT: ') : ''; if (isSpanish) { return `${urgency}¡Ayuda a salvar a ${petName}! 🐾 Este increíble animal necesita nuestro apoyo. ¡Cada compartir cuenta! #RescateDeAnimales #SalvaUnaVida #Toto`; } else { return `${urgency}Help save ${petName}! 🐾 This amazing animal needs our support. Every share counts! #PetRescue #SaveALife #Toto`; } } generateHashtags(context, isSpanish) { const baseHashtags = isSpanish ? ['#RescateDeAnimales', '#SalvaUnaVida', '#Toto', '#AdoptaNoCompres'] : ['#PetRescue', '#SaveALife', '#Toto', '#AdoptDontShop']; // Animal-specific hashtags if (context.animalType === 'dog') { baseHashtags.push(isSpanish ? '#PerrosEnAdopcion' : '#DogsOfInstagram', '#RescueDog'); } if (context.animalType === 'cat') { baseHashtags.push(isSpanish ? '#GatosEnAdopcion' : '#CatsOfInstagram', '#RescueCat'); } // Urgency hashtags if (context.emergency) { baseHashtags.push(isSpanish ? '#Emergencia' : '#Emergency', '#Urgent'); } // Location hashtags if (context.location) { baseHashtags.push(`#${context.location.replace(/\s+/g, '')}`); } return baseHashtags; } getRecommendedPlatforms(context) { // Base platforms for all content const platforms = ['Facebook', 'Instagram']; // Add platform-specific recommendations based on content if (context.hasPhotos || context.hasVideo) { platforms.push('Instagram', 'TikTok'); } if (context.emergency) { platforms.push('Twitter'); // Better for urgent updates } if (context.localCommunity) { platforms.push('WhatsApp', 'Nextdoor'); } return Array.from(new Set(platforms)); // Remove duplicates } getLocalizedNextSteps(isSpanish) { return isSpanish ? [ 'Elegir plataforma para compartir', 'Personalizar mensaje', 'Agregar historia personal', 'Etiquetar amigos que puedan ayudar' ] : [ 'Choose platform to share', 'Customize share message', 'Add personal story', 'Tag friends who might help' ]; } identifyTargetAudience(context, userContext) { const audiences = []; if (context.animalType === 'dog') audiences.push('dog lovers'); if (context.animalType === 'cat') audiences.push('cat lovers'); if (context.young || context.puppy || context.kitten) audiences.push('families'); if (context.emergency) audiences.push('emergency responders'); if (userContext.location) audiences.push(`${userContext.location} locals`); return audiences.join(', ') || 'general pet lovers'; } assessViralPotential(context) { let score = 0; // Content quality factors if (context.hasPhotos || context.hasVideo) score += 2; if (context.emergency) score += 2; if (context.heartwarming || context.rescue) score += 1; if (context.animalType === 'dog' || context.animalType === 'cat') score += 1; if (context.young || context.puppy || context.kitten) score += 1; // Engagement factors if (context.previousShares > 50) score += 1; if (context.engagementRate > 0.05) score += 1; // 5%+ engagement if (score >= 6) return 'high'; if (score >= 3) return 'medium'; return 'low'; } } class DialogAgent extends BaseAgent { constructor(apiKey) { super(apiKey, exports.AgentType.DIALOG_AGENT); } async process(userMessage, context, intent) { // Build user context for dynamic response const userContext = this.buildUserContext(context, userMessage); // Check if this is a summary request const isSummaryRequest = userMessage.toLowerCase().includes('summary') || userMessage.toLowerCase().includes('summarize') || userMessage.toLowerCase().includes('tell me about') || userMessage.toLowerCase().includes('resume') || userMessage.toLowerCase().includes('resumen') || userMessage.toLowerCase().includes('cuéntame') || userMessage.toLowerCase().includes('dame un resumen'); // Check for urgency/status questions const isUrgencyQuestion = userMessage.toLowerCase().includes('urgent') || userMessage.toLowerCase().includes('urgente') || userMessage.toLowerCase().includes('por qué') || userMessage.toLowerCase().includes('why'); let dialogPrompt; if (isSummaryRequest && context.description) { // Language-aware summary generation with ANTI-REPETITION focus const isSpanish = userContext.language === 'es'; dialogPrompt = ` ANTI-REPETITION PRIORITY: This pet already has a description. Your job is to CREATE FRESH CONTENT that adds value beyond what's already written. Pet: ${context.petName || 'this pet'} Status: ${context.status || 'active'} Current description: "${context.description}" Emergency status: ${context.emergency ? 'URGENT - needs immediate help' : 'standard case'} CREATE A COMPLETELY NEW summary that: - NEVER repeats phrases from the original description - Focuses on EMOTIONAL CONNECTION (how this pet makes you feel) - Emphasizes SPECIFIC ACTION STEPS the user can take RIGHT NOW - Uses different vocabulary than the original - Highlights what makes THIS case special/unique - Language: ${isSpanish ? 'Spanish (Argentina) - use "vos" when appropriate' : 'English'} - Format: 2 short paragraphs, max 35 words each - DO NOT mention "donar" and "compartir" in the same response - pick ONE priority action ${isSpanish ? 'Si hay contexto de caso, termina con "¿Qué querés hacer para ayudar a [NombreMascota]? ¡Tocá los íconos para ayudar!" Si no hay caso, termina con "¿Cómo puedo ayudarte más?" - NUNCA menciones sitios web.' : 'If case context exists, end with "What do you want to do to help [PetName]? Tap the icons to help!" If no case, end with "How can I help you further?" - never mention websites.'}`; } else if (isUrgencyQuestion && context.status === 'urgent') { // Specific urgency explanation with medical details const isSpanish = userContext.language === 'es'; dialogPrompt = ` User is asking WHY this case is urgent. Give a SPECIFIC medical/situational explanation. Pet: ${context.petName || 'this pet'} Description: "${context.description || 'No specific details available'}" Status: ${context.status} Create a focused urgency exp